Thursday, July 3, 2008

Fun with C# 3.0 (#3) - Lambda expressions

The addition of LINQ to C#/.NET has really improved the way that we can handle data in our applications. Here's an example.

Suppose we want to write a method to count the number of strings in a list which exceed a certain length. It's not to hard to do with C# 2.0; you iterate over the items, make the comparison, increment a counter and return it. But C# 3.0 makes it simple.

When the namespace System.Linq is included in your file, IEnumerable objects get a load of new extension methods. One of them is Where, which will return a sub-collection of items that match a filter. The filter is defined as a lambda expression that returns a bool. If the item causes the lambda expression to evaluate to true, then the item will be included in the sub-collection. Let's look at some code:

public int NumberLongerThan(List<string> list, int n)
var subList = list.Where(
(string str) => str.Length > n
return subList.Count();

Here we pass in a List<string> and a number. The subList will have the string items that are longer than n characters. Note that we don't have to define the type of subList; the compiler can infer it at compile-time.

Here is the code that uses the method.

List<string> list = new List<string>() { "abc", "def", "GH", "ijKL", "mnop", "QRSTUV", "Wxyz" };
Console.WriteLine(NumberLongerThan(list, 0));
Console.WriteLine(NumberLongerThan(list, 1));
Console.WriteLine(NumberLongerThan(list, 2));
Console.WriteLine(NumberLongerThan(list, 3));

This is the lambda expression:

(string str) => str.Length > n

It states that a string is passed in and returns a bool based on the test of the string Length. The Where() method takes an expression that expects a string and returns a bool. Since the compiler knows this, we can let it infer the type of the parameter and specify the expression like so:

str => str.Length > n

Also, note that if we have a named method that is of the correct delegate type (Func<T, bool>), we can pass it to Where(). Here, List<T> is List<string>, so the method must take a string parameter.

public bool TestString(string s) { return s.Length > 3; }

var filtered = list.Where(TestString);
//-- or --
Func<string, bool> f = TestString;
var list2 = list.Where(f);

The advantage of using the unnamed lambda expression is that we can reference the local variable n as a parameter in our test expression.

More info at Charlie Calvert's blog.

No comments: