22

在处理类似 a 的事情时,List<string>您可以编写以下内容:

list.ForEach(x => Console.WriteLine(x));

或者您可以使用方法组来执行相同的操作:

list.ForEach(Console.WriteLine);

我更喜欢第二行代码,因为它对我来说看起来更干净,但这有什么好处吗?

4

7 回答 7

27

好吧,让我们来看看会发生什么。

static void MethodGroup()
{
    new List<string>().ForEach(Console.WriteLine);
}

static void LambdaExpression()
{
    new List<string>().ForEach(x => Console.WriteLine(x));
}

这被编译成下面的 IL。

.method private hidebysig static void MethodGroup() cil managed
{
    .maxstack 8
    L_0000: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
    L_0005: ldnull 
    L_0006: ldftn void [mscorlib]System.Console::WriteLine(string)
    L_000c: newobj instance void [mscorlib]System.Action`1<string>::.ctor(object, native int)
    L_0011: call instance void [mscorlib]System.Collections.Generic.List`1<string>::ForEach(class [mscorlib]System.Action`1<!0>)
    L_0016: ret 
}

.method private hidebysig static void LambdaExpression() cil managed
{
    .maxstack 8
    L_0000: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
    L_0005: ldsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1
    L_000a: brtrue.s L_001d
    L_000c: ldnull 
    L_000d: ldftn void Sandbox.Program::<LambdaExpression>b__0(string)
    L_0013: newobj instance void [mscorlib]System.Action`1<string>::.ctor(object, native int)
    L_0018: stsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1
    L_001d: ldsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1
    L_0022: call instance void [mscorlib]System.Collections.Generic.List`1<string>::ForEach(class [mscorlib]System.Action`1<!0>)
    L_0027: ret 
}

请注意方法组方法如何创建Action<T>一个一次性使用的委托,而 lambda 表达式方法如何创建一个隐藏的匿名委托字段并在必要时对其进行内联初始化。通知brtrue指示IL_000a

于 2010-10-01T18:56:59.217 回答
12

使用 lambda 表达式时有一个额外的间接级别。正如其他人所提到的,使用这样的非闭包表达式,您只需在中间有一个额外的方法调用。

虽然有一些有趣的差异。在第二种情况下,每次调用都会创建一个新的委托实例。对于前者,委托创建一次并缓存为隐藏字段,因此如果您调用很多,您将节省分配。

此外,如果你在 lambda 表达式中引入一个局部变量,它就会变成一个闭包,而不仅仅是生成一个局部方法,而是会创建一个新类来保存这些信息,这意味着那里有额外的分配。

于 2010-10-01T18:28:06.157 回答
9

正如其他人所指出的,lambda 会导致额外的不必要的间接层。但是,也存在细微的语言差异。例如,在 C# 3 中,泛型类型推断的工作方式与M(F)尝试M(x=>F(x))执行返回类型推断时不同。

详情见:

https://docs.microsoft.com/en-us/archive/blogs/ericlippert/c-3-0-return-type-in​​ference-does-not-work-on-method-groups

和后续行动:

https://docs.microsoft.com/en-us/archive/blogs/ericlippert/method-type-in​​ference-changes-part-zero

于 2010-10-01T23:37:03.043 回答
7

我相信这是有好处的。在第一种情况下,您正在创建调用Console.Writeline(string)函数的匿名方法,而在另一种情况下,您只是将引用传递给现有函数。

于 2010-10-01T18:24:51.110 回答
3

是的; 第一个实际上可能导致发生不必要的额外临时调用;传入x一个简单调用的方法Console.WriteLine(x);您不需要做第一个,因为 Console.WriteLine 已经是一个与 ForEach 正在寻找的签名匹配的方法。

于 2010-10-01T18:23:47.987 回答
0

就我个人而言,我也更喜欢第二种,因为它对调试的混淆更少,但在这种情况下,我认为这只是风格问题,因为它们最终都完成了同样的事情。

于 2010-10-01T18:24:19.583 回答
0

除了让喜欢方法组的人更愉快之外,没有任何切实的好处,而让不喜欢他们的人感到恼火[如果你喜欢的话。]此外,它使你的代码与早期的编译器不兼容。

-Oisin

于 2010-10-01T18:24:30.510 回答