Thursday, February 22, 2007

kick it on DotNetKicks.com


Last time, I wrote about the conciseness of C# 3.0 lambda expressions being a distinct advantage over C# 2.0 anonymous methods. This time, I'm going to look at something a bit more subtle that, in some situations, helps us write more elegant code if lambda expressions are used instead of anonymous methods. But first, I need to talk about generic type inference.

The idea of generic type inference is this: when calling a generic method (that is, a method that defines its own generic type parameters) you can leave off the generic type arguments and the compiler will try to infer what they should be based on the parameters that are passed to the method. For example, assume that I have a generic method like this:

public static void Display<T>(IEnumerable<T> items)
{
  if (items == null)
    return;

  foreach (T item in items)
    Console.WriteLine(item);
}

Naturally, I can call the method with code like this:

Display<int>(new int[] { 10, 1, 9, 2, 8, 3, 7, 4, 6, 5 });

However, I could also call the method without specifying the generic type argument and allow the compiler to infer it:

Display(new int[] { 10, 1, 9, 2, 8, 3, 7, 4, 6, 5 });

Because an array of ints is being passed into the Display<T> method and that array implements IEnumerable<int>, the compiler is able to determine that "T" should be replaced with "int". This is a fantastic feature because it allows us to write code that isn't littered with angle brackets.

Now, consider the code with anonymous methods from my last post:

int[] numbers = { 10, 1, 9, 2, 8, 3, 7, 4, 6, 5 };

int[] lessThanFive = Array.FindAll<int>(numbers, delegate(int n) { return n < 5; });

int sum;
Array.ForEach<int>(lessThanFive, delegate(int n) { sum += n });

Console.WriteLine(sum);

If we allow the compiler to infer the types of the generic parameters, the code can be rewritten like this:

int[] numbers = { 10, 1, 9, 2, 8, 3, 7, 4, 6, 5 };

int[] lessThanFive = Array.FindAll(numbers, delegate(int n) { return n < 5; });

int sum;
Array.ForEach(lessThanFive, delegate(int n) { sum += n });

Console.WriteLine(sum);

Cool. OK, let's look at one last example.

An extremely useful generic method on the Array class is Array.ConvertAll<TInput, TOutput>. This essentially allows you to take an array of one type and convert it to an array of another type. Or, you could convert an array to a new array of the same type and just perform some sort of transformation on the elements of the array. Here's the method signature:

public static void ConvertAll<TInput, TOutput>(TInput[] array, Converter<TInput, TOutput> converter);

Here's the signature of the Converter<TInput, TOutput> delegate type it uses:

public delegate TOutput Converter<TInput, TOutput>(TInput input);

And, here's some C# 2.0 code that demonstrates this handy method by converting an array of ints to an array of strings:

int[] numbers = { 10, 1, 9, 2, 8, 3, 7, 4, 6, 5 };

string[] numberText = Array.ConvertAll<int, string>(numbers, delegate(int n) { return n.ToString(); });

Declaring the generic type arguments when calling this method is clunky and takes up a fair bit of code real estate (13 characters in this example). Fortunately, we can remove those type arguments and let the compiler infer them, right? Wrong. It turns out that this will result in a compiler error:

int[] numbers = { 10, 1, 9, 2, 8, 3, 7, 4, 6, 5 };

string[] numberText = Array.ConvertAll(numbers, delegate(int n) { return n.ToString(); });

Try compiling that code and you'll get this error: "The type arguments for method 'System.Array.ConvertAll<TInput,TOutput>(TInput[], System.Converter<TInput,TOutput>)' cannot be inferred from the usage. Try specifying the type arguments explicitly." What's going on here? The problem is with the "TOutput" generic type parameter.

Look careful at the definition of the ConvertAll method and the Converter delegate type. "TInput" is easy for the compiler to infer because it is used directly in a parameter to ConvertAll. However, "TOutput" is only used as the return type of the Converter delegate and, unfortunately, the C# compiler ignores anonymous methods when inferring types. So, the type inference fails and we are forced to use less-than-ideal code.

Fortunately, lambda expressions are treated differently than anonymous methods by the C# compiler's generic type inference algorithm. If you want to see the details of how this algorithm works (which are fascinating), check out a video of Eric Lippert explaining them here.

If we simply convert our anonymous method to a lambda expression, we get a successful compile:

int[] numbers = { 10, 1, 9, 2, 8, 3, 7, 4, 6, 5 };

string[] numberText = Array.ConvertAll(numbers, n => n.ToString());

Concise. Elegant. Cool.

Lambdas: 2
Anonymous Methods: 0


kick it on DotNetKicks.com

posted on Thursday, February 22, 2007 6:10:30 AM (Pacific Standard Time, UTC-08:00)  #    Comments [2]

kick it on DotNetKicks.com