views:

240

answers:

1

I discovered something very strange that I'm hoping to better understand.

var all = new List<int[]>{
                new int[]{1,2,3},
                new int[]{4,5,6},
                new int[]{7,8,9}
              };

all.ForEach(n => n.ForEach(i => Console.WriteLine(i)));

which can be rewritten as:

...
all.ForEach(n => n.ForEach(Console.WriteLine));

How is it possible to leave out the lambda expression parameter (i=>) and still have the current item passed to console.WriteLine?

Thanks for any insight. -Keith

+9  A: 

List<T>.ForEach is looking for an Action<T>. When you write

n.ForEach(Console.WriteLine);

what you have here is one of the members of the method group Console.WriteLine playing the role of an Action<T>. The compiler will look for the best overload of Console.WriteLine that eats instances of int. In fact, it will use the overload Console.WriteLine(int). It will then use this overload to play the role of an Action<int>.

For details on how this is done, see §6.6 of the specification (Method group conversions).

However, when you write

n.ForEach(i => Console.WriteLine(i));

we actually have a very different Action<int> In the first case, the Action<int> was Console.WriteLine(int). Here, the Action<int> is equivalent to you having written

public static void DoSomething(int i) {
    Console.WriteLine(i);
}

and then

n.ForEach(DoSomething);

(Of course, the compiler has to go through the same method group process as described above to figure out what is meant by DoSomething).

The point is that in the first case the Action<int> is Console.WriteLine(int). However, in the second case the Action<int> is a middle man (the lambda expression) that itself will call Console.WriteLine(int).

Jason
++ for imagery of the compiler "eating" an overload of Console.WriteLine!
Terry Donaghe
Very nicely articulated. Thank you!
Keith
Nice explanation.
astander