Reputation: 355
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
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
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
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
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
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