
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
