lJohnson
lJohnson

Reputation: 355

Closures and Lambda in C#

I get the basic principles of closures and lambda expressions but I'm trying to wrap my mind around what is happening behind the scenes and when it is/isn't practical to use them in my code. Consider the following example, that takes a collection of names and returns any names that begin with the letter C...

    static void Main(string[] args)
    {
        List<string> names = new List<string>();
        names.AddRange(new string[]
        {
            "Alan", "Bob", "Chris", "Dave", "Edgar", "Frank"
        });


        names.FindAll(x => x.StartsWith("C")).ForEach(
            i => Console.WriteLine(i));

    }

First, is there a more direct way that I could have written this expression? Secondly, isn't "FindAll" going to allocate memory for a new collection that holds the matching items? I definitely see that the syntax is more elegant, but I want to make sure I'm not walking into performance issues later down the road when working with larger collections. Does the compiler do some optimization voodoo behind the scenes that makes my concerns invalid?

Upvotes: 11

Views: 3161

Answers (5)

Joel Coehoorn
Joel Coehoorn

Reputation: 415600

Yes, FindAll will create a new list. You want "Where", which will return an IEnumerable object that knows how to loop over your existing list:

foreach (string name in names.Where(n => n.StartsWith("C") ) ) 
{
    Console.WriteLine(name);
}

But there's no closure in that code, because there's no local variable to capture.

Upvotes: 15

XXXXX
XXXXX

Reputation: 1096

What makes an expression specifically a closure is lexical scoping, no?

string prefix = "C";  
// value of prefix included in scope  
names.FindAll(x => x.StartsWith(prefix)).ForEach(...);    

or even

Func filter = null;

{
    string prefix = "C";
    // value of prefix included in scope
    filter = x => x.StartsWith (prefix);    
}

// find all names starting with "C"
names.FindAll (filter).ForEach (...);       

Or am I missing something or making unwarranted assumptions?

Upvotes: 0

Eric Lippert
Eric Lippert

Reputation: 659956

The other answers that say to use "Where" are correct. An additional point: you can also use query comprehension syntax to make the "Where" look nicer:

   var query = from name in names where name.StartsWith("C") select name;
   foreach(var result in query) Console.WriteLine(result);

Note that as a stylistic concern, I recommend that expressions have no side effects and statements always have side effects. Therefore I personally would use a foreach statement rather than a ForEach subexpression to perform the output side effect. Many people disagree with this, but I think it makes the code more clear.

Upvotes: 12

LukeH
LukeH

Reputation: 269278

You're right that using the List<T>.FindAll method will create and return a new List<T>.

If you're able to use LINQ then there are many methods that stream their results one item at a time, where possible, rather than returning a fully populated collection:

foreach (var i in names.Where(x => x.StartsWith("C")))
{
    Console.WriteLine(i);
}

There's no built-in ForEach method that acts on IEnumerable<T>, but it's trivial to write your own extension if you really need that functionality:

names.Where(x => x.StartsWith("C")).ForEach(Console.WriteLine);

// ...

public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
    foreach (T item in source)
    {
        action(item);
    }
}

Upvotes: 2

Adam Robinson
Adam Robinson

Reputation: 185593

You should use Where instead of FindAll. Where will iterate over the collection for your condition and allow you to execute your action, rather than creating a new collection that meets your condition, then iterating over THAT and executing your action.

Upvotes: 2

Related Questions