即使只有一个参数,也有理由偏爱lambda语法吗?


14
List.ForEach(Console.WriteLine);

List.ForEach(s => Console.WriteLine(s));

对我来说,区别仅仅是纯粹的外观,但是有什么微妙的原因为什么一个可能比另一个更受欢迎?


以我的经验,每当第二个版本似乎更可取时,通常是因为该方法的命名不正确。
罗曼·赖纳

Answers:


23

通过ILSpy查看编译后的代码,实际上两个引用之间存在差异。对于这样的简单程序:

namespace ScratchLambda
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    internal class Program
    {
        private static void Main(string[] args)
        {
            var list = Enumerable.Range(1, 10).ToList();
            ExplicitLambda(list);
            ImplicitLambda(list);
        }

        private static void ImplicitLambda(List<int> list)
        {
            list.ForEach(Console.WriteLine);
        }

        private static void ExplicitLambda(List<int> list)
        {
            list.ForEach(s => Console.WriteLine(s));
        }
    }
}

ILSpy将其反编译为:

using System;
using System.Collections.Generic;
using System.Linq;
namespace ScratchLambda
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            List<int> list = Enumerable.Range(1, 10).ToList<int>();
            Program.ExplicitLambda(list);
            Program.ImplicitLambda(list);
        }
        private static void ImplicitLambda(List<int> list)
        {
            list.ForEach(new Action<int>(Console.WriteLine));
        }
        private static void ExplicitLambda(List<int> list)
        {
            list.ForEach(delegate(int s)
            {
                Console.WriteLine(s);
            }
            );
        }
    }
}

如果您同时查看两者的IL调用堆栈,则Explicit实现会有更多调用(并创建一个生成的方法):

.method private hidebysig static 
    void ExplicitLambda (
        class [mscorlib]System.Collections.Generic.List`1<int32> list
    ) cil managed 
{
    // Method begins at RVA 0x2093
    // Code size 36 (0x24)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldsfld class [mscorlib]System.Action`1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    IL_0006: brtrue.s IL_0019

    IL_0008: ldnull
    IL_0009: ldftn void ScratchLambda.Program::'<ExplicitLambda>b__0'(int32)
    IL_000f: newobj instance void class [mscorlib]System.Action`1<int32>::.ctor(object, native int)
    IL_0014: stsfld class [mscorlib]System.Action`1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'

    IL_0019: ldsfld class [mscorlib]System.Action`1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    IL_001e: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::ForEach(class [mscorlib]System.Action`1<!0>)
    IL_0023: ret
} // end of method Program::ExplicitLambda


.method private hidebysig static 
    void '<ExplicitLambda>b__0' (
        int32 s
    ) cil managed 
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    // Method begins at RVA 0x208b
    // Code size 7 (0x7)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: call void [mscorlib]System.Console::WriteLine(int32)
    IL_0006: ret
} // end of method Program::'<ExplicitLambda>b__0'

虽然隐式实现更加简洁:

.method private hidebysig static 
    void ImplicitLambda (
        class [mscorlib]System.Collections.Generic.List`1<int32> list
    ) cil managed 
{
    // Method begins at RVA 0x2077
    // Code size 19 (0x13)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldnull
    IL_0002: ldftn void [mscorlib]System.Console::WriteLine(int32)
    IL_0008: newobj instance void class [mscorlib]System.Action`1<int32>::.ctor(object, native int)
    IL_000d: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::ForEach(class [mscorlib]System.Action`1<!0>)
    IL_0012: ret
} // end of method Program::ImplicitLambda

请注意,这是快速草稿程序发布的代码,因此可能还有进一步优化的空间。但这是Visual Studio的默认输出。
Agent_9191 2011年

2
+1这是因为lambda语法实际上是无缘无故地将原始方法调用包装在匿名函数<i>中。这是完全没有意义的,因此,当可用时,应将原始方法组用作Func <>参数。
艾德·詹姆斯

哇,您得到了绿色的勾号,用于研究!
Benjol 2011年

2

一般而言,我更喜欢lambda语法。当您看到它时,它会告诉您类型是什么。当看到时Console.WriteLine,您必须询问IDE是什么类型。当然,在这个琐碎的示例中,这是显而易见的,但是在一般情况下,可能不会那么多。


我更喜欢labmda语法,以便与需要的情况保持一致。
bunglestink 2011年

4
我不是C#人员,但是在我用于lambda的语言(JavaScript,Scheme和Haskell)中,人们可能会给您相反的建议。我认为这说明了良好的风格与语言有关。
迪洪·耶维斯

它以什么方式告诉您类型?当然,您可以明确地知道lambdas参数的类型,但是这样做并不常见,在这种情况下是
不可行的

1

你给的两个例子他们的不同之处在于

List.ForEach(Console.WriteLine) 

您实际上是在告诉ForEach循环使用WriteLine方法

List.ForEach(s => Console.WriteLine(s));

实际上是在定义foreach会调用的方法,然后您告诉它在那里处理什么。

因此,对于简单的一个内衬而言,如果您要调用的方法具有与已经调用的方法相同的签名,则我不希望定义lambda,我认为它更具可读性。

对于具有不兼容lambda的方法,绝对是一个不错的方法,前提是它们不会过于复杂。


1

有强烈的理由选择第一行。

每个委托都有一个Target属性,即使实例超出范围,该属性也允许委托引用实例方法。

public class A {
    public int Data;
    public void WriteData() {
        Console.WriteLine(this.Data);
    }
}

var a1 = new A() {Data=4};
Action action = a1.WriteData;
a1 = null;

我们无法致电,a1.WriteData();因为它a1为null。但是,我们可以action毫无问题地调用委托,并且它将打印print 4,因为它action拥有对应调用该方法的实例的引用。

当匿名方法在实例上下文中作为委托传递时,即使不明显,委托仍将保留对包含类的引用:

public class Container {
    private List<int> data = new List<int>() {1,2,3,4,5};
    public void PrintItems() {
        //There is an implicit reference to an instance of Container here
        data.ForEach(s => Console.WriteLine(s));
    }
}

在这种特定情况下,可以合理地假设.ForEach未在内部存储委托,这意味着Container仍保留其实例及其所有数据。但是没有保证。接收委托的方法可能会无限期保留委托和实例。

另一方面,静态方法没有实例可参考。以下内容不会隐式引用的实例Container

public class Container {
    private List<int> data = new List<int>() {1,2,3,4,5};
    public void PrintItems() {
        //Since Console.WriteLine is a static method, there is no implicit reference
        data.ForEach(Console.WriteLine);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.