views:

493

answers:

5

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?

+10  A: 

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.

Joel Coehoorn
Ok, so maybe I don't quite get the basics of closures yet. Nevertheless, all the answers I got here are great and move me a little farther down the road... thanks, everyone.
lJohnson
+2  A: 

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.

Adam Robinson
+2  A: 

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);
    }
}
LukeH
This is exactly how I was going to do it. I would have also mentioned that the list creation can be simplified: `List<string> names = new List<string>{"Alan", "Bob", "Chris", "Dave", "Edgar", "Frank"};`
Ryan Versaw
+6  A: 

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.

Eric Lippert
That's a great convention!
Craig Stuntz
A: 

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?

XXXXX