I was working on some code today, that was trying to find a string in a list of strings. I came up with a neat way to express it using a lambda expression.
The original code looked something like this:
public static void f( List<string> assemblies )
{
// ...
if (assemblies.Contains(file.Name))
// ...
}
The problem was that the list of assemblies could be of mixed case, as could file.Name, and I wanted to find a case insensitive match.
There are a thousand ways to do this, but here is what I did:
if (assemblies.Exists( s => String.Compare( s, file.Name, true ) == 0 ))
This works because List<T> has an Exists method that you can pass a delegate of type Predicate<T> :
Predicate<T> is simply a function that is called with an instance of type T and returns a boolean result.
It is intended to be used to test some condition on each instance of type T in some enumeration of T. For instance, in the implementation of Exists<T>, for each object in the list/enumeration, the predicate passed in as a delegate is called with the current object as its parameter, and if the delegate returns true, then Exists returns true and breaks its loop.
So, we can choose to pass a regular delegate, an anonymous delegate, or a lambda expression that match the Predicate<T> signature. Above, I used the lambda expression form:
s => String.Compare( s, file.Name, true ) == 0
Since 'assemblies' in my code is a List<string>, then we need to implement a Predicate<string>, meaning a piece of code that takes a string and returns a bool. The string parameter is represented by the 's' in the expression above, and what I want to do is compare each string s in assemblies to the file.Name I am looking for, and return true if they are the same (with no case sensitivity) so my String.Compare( s, file.Name ) == 0 does that for me.
This could have been written equivalently as this using an anonymous delegate:
if (assemblies.Exists( delegate( string s ) { return String.Compare( s, file.Name, true ) == 0; } ))
Or like this using a regular delegate:
static string m_FileName;
public static bool Compare(string s)
{
return String.Compare(s, m_FileName, true) == 0;
}
public static void f( List<string> assemblies)
{
// ...
m_FileName = file.Name;
if (assemblies.Exists( new Predicate<string>( Compare )))
// ..
}
Note that in this case we have some new complications: in the first two implementations, using lambda expressions or anonymous delegates, the inline code could reference file.Name, as it was in scope, whereas with the regular delegate, that is not possible, and we have to go to the extra effort of saving file.Name to a member variable first so it can be referenced inside of Compare (and note that this in turn introduces thread safety issues, what if two threads call this code on the same instance?). The fact that these methods are static also forces the variable and delegate to be static too.
But I think you will agree by now that the lambda expression makes for some pretty compelling code? The regular delegate approach pretty much obfuscates your intent, and has the logic spread out between two methods and one member variable, the anonymous delegate keeps the code more well shaped but is syntactically ugly, whereas, to go back to our original lambda expression form, once you get used to it, this is very much to the point, n'est pas?
if (assemblies.Exists( s => String.Compare( s, file.Name, true ) == 0 ))