C# method group strangeness

2019-01-26 05:45发布

问题:

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

回答1:

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).



回答2:

This is less confusing if you consider what is really happening.

You are passing a method to a delegate argument. Most of the time, we think of delegates in the context of events, but they can be parameters to methods as well. It doesn't seem odd when a method is added to an event without arguments, it just is unusual looking when executed in this context.

Before lambdas, you had to do this all the time, and it was such a pain that one would never consider using a library that looked like LINQ. With Lambdas, this is easier to do, but you can always do the old way as well.