C#方法组的奇怪行为

13

我发现了一些非常奇怪的事情,希望能更好地理解。

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

可以重新写成以下形式:

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

如何不使用 lambda 表达式参数 (i=>),仍然将当前项传递给 console.WriteLine?

感谢任何见解。 -Keith

2个回答

33

List<T>.ForEach正在寻找一个Action<T>。当你写

n.ForEach(Console.WriteLine);

这里有一个方法组成员Console.WriteLine扮演了Action<T>的角色。编译器会寻找最佳匹配的Console.WriteLine重载,以接收int实例。实际上,它将使用Console.WriteLine(int)这个重载。然后,它将使用此重载来扮演Action<int>的角色。
关于如何完成这个过程的详细信息,请参见规范书第6.6节(方法组转换)。
但是,当您编写以下内容时:
n.ForEach(i => Console.WriteLine(i));

我们实际上有一个非常不同的Action<int>。在第一种情况下,Action<int>Console.WriteLine(int)。在这里,Action<int>等同于您编写的内容。

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

然后

n.ForEach(DoSomething);

当然,编译器必须通过与上述方法组过程相同的过程来确定DoSomething的含义。

重点是在第一种情况下,Action<int> Console.WriteLine(int)。但是,在第二种情况下,Action<int>是一个中间人(lambda表达式),它本身将调用Console.WriteLine(int)


1
++ 为编译器“吞噬”Console.WriteLine的过载提供形象化的描绘! - Tad Donaghe
1
非常清晰明了的表达。谢谢! - Keith

2

如果您考虑实际发生的情况,这就不那么令人困惑了。

您正在将一个方法传递给委托参数。大多数情况下,我们认为委托是在事件上下文中使用的,但它们也可以作为方法的参数。当一个没有参数的方法被添加到事件中时,这似乎并不奇怪,只有在执行此操作时看起来有些不寻常。

在Lambda之前,您必须一直这样做,而且这非常麻烦,以至于您永远不会考虑使用类似LINQ的库。使用Lambda,这变得更容易了,但您仍然可以按照旧方式进行操作。


C# 2.0有匿名委托语法(delegate(){}),但它仍然不够简洁,会让LINQ看起来相当混乱。 - Gabe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接