【问题标题】:Are there any benefits to using a C# method group if available?如果可用,使用 C# 方法组有什么好处吗?
【发布时间】:2021-04-26 09:08:45
【问题描述】:

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

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

或者你可以使用一个方法组来做同样的操作:

list.ForEach(Console.WriteLine);

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

【问题讨论】:

  • 嗯,ReSharper 推荐第二个版本。所以它应该是正确的......
  • “更聪明的人说那是对的”并不是对 WHY 的真正解释。可能是对的。这可能是最好的答案。但这并不能回答“为什么”的问题。

标签: c# method-group


【解决方案1】:

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

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&lt;T&gt; 委托,而 lambda 表达式方法如何创建一个隐藏的匿名委托字段并在必要时对其进行内联初始化。注意brtrueIL_000a的指令。

【讨论】:

  • 如何查看编译后的IL?
  • @M.H.:使用 ILDASM 或 Reflector。
  • @M.H 你可以使用LINQPad查看编译好的IL
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

    详情见:

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

    以及后续:

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 是的,这也是我的感觉。我想优化器可能会认识到这一点并去除额外的、不需要的调用,但是当以“更好”的方式编写它实际上更容易时,这样做是有意义的,IMO。我写了一篇关于这个主题的博文(不必要地使用了像这样的 Lambda 表达式:andrewbarber.com/post/…
      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

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

          -奥辛

          【讨论】:

            猜你喜欢
            • 2020-10-20
            • 1970-01-01
            • 2011-04-24
            • 1970-01-01
            • 2021-04-10
            • 1970-01-01
            • 2014-10-26
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多