Lambda表达式和方法组的区别

20

什么是两者之间的区别?

Class1.Method1<Guid, BECustomer>("cId", Facade.Customers.GetSingle);
并且
Class1.Method1<Guid, BECustomer>("cId", x => Facade.Customers.GetSingle(x));

Resharper建议使用第一个表达式。


在C#中,没有语义上的区别。在Java中,类似类型的事情会导致'this'的不同绑定!我经常被这个问题困扰... - Sprague
3个回答

25

就结果而言,两者没有区别。但是第二种方式会创建一个额外的重定向:代码首先会调用一个以一个名为x的参数命名的匿名方法,然后该方法再使用该参数调用Facade.Customers.GetSingle。这个重定向完全没有好处,这就是为什么ReSharper建议您使用第一种选择。


1
没错,我认为这只是针对只有一个参数的lambda表达式的语法糖。 - Vladislav Zorov
4
@Vladislav:您的意思是什么?第一种版本不是第二种版本的语法糖。第一种版本传递了“GetSingle”方法的“函数指针”,而第二种版本传递了匿名方法的“函数指针”。 - Daniel Hilgarth
3
它不仅限于单参数 Lambda 表达式,方法组也适用于任何匹配的委托签名。 - dahlbyk
16
关于闭包有一个区别。假设Facade.Customers会随时间变化而改变。那么第一个(来自命名方法的方法组)将保留原始Customers,而第二个(来自Lambda表达式的匿名函数)将反映新的CustomersCustomers由匿名函数重新计算)。因此,在第二个中,评估是“延迟”的。如果这很难理解,请尝试运行此代码:string test = "12345"; Func<string, bool> f = test.Contains; Func<string, bool> g = x => test.Contains(x); test = "changed!"; bool a = f("34"); bool b = g("34"); - Jeppe Stig Nielsen
对于只允许方法组转换(而不是lambda)的示例,这里有一个带有ref参数的示例。也就是说,代码static void M1(ref string s) { Func<string, bool> f1 = s.Contains; /* OK */ }是可以的;但是代码static void M2(ref string s) { Func<string, bool> f2 = x => s.Contains(x); /* Compile-time error, cannot close over 'ref' parameter */ }是不合法的。 - Jeppe Stig Nielsen
显示剩余3条评论

16

在幕后,如果您使用lambda表达式,编译器将生成更多的代码。但是对于方法组,它只创建一个指向该方法的新委托:

L_0001: ldstr "cId"
L_0006: ldnull 
L_0007: ldftn void Facade/Customers::GetSingle(valuetype [mscorlib]System.Guid)
L_000d: newobj instance void [mscorlib]System.Action`1<valuetype [mscorlib]System.Guid>::.ctor(object, native int)
L_0012: call void Class1::Method1<valuetype [mscorlib]System.Guid, class BECustomer>(string, class [mscorlib]System.Action`1<!!0>)

使用Lambda表达式,将在类上创建一个匿名方法(<Test>b__0在L_0025上),并且委托引用它:

L_0018: ldstr "cId"
L_001d: ldsfld class [mscorlib]System.Action`1<valuetype [mscorlib]System.Guid> Class1::CS$<>9__CachedAnonymousMethodDelegate1
L_0022: brtrue.s L_0037
L_0024: ldnull 
L_0025: ldftn void Class1::<Test>b__0(valuetype [mscorlib]System.Guid)
L_002b: newobj instance void [mscorlib]System.Action`1<valuetype [mscorlib]System.Guid>::.ctor(object, native int)
L_0030: stsfld class [mscorlib]System.Action`1<valuetype [mscorlib]System.Guid> Class1::CS$<>9__CachedAnonymousMethodDelegate1
L_0035: br.s L_0037
L_0037: ldsfld class [mscorlib]System.Action`1<valuetype [mscorlib]System.Guid> Class1::CS$<>9__CachedAnonymousMethodDelegate1
L_003c: call void Class1::Method1<valuetype [mscorlib]System.Guid, class BECustomer>(string, class [mscorlib]System.Action`1<!!0>)

那么在Lambda表达式和方法组之间是否存在实际的性能差异呢?我可以看到多了5行IL代码,但我们在谈论什么样的现实世界差异呢?对于非常大的数据集来说,可能只是几个时钟周期的差别吗? - tap
1
最糟糕的情况是在大型集合中为每个项目调用您的“Method1” - 匿名类的新实例将在每次迭代中创建。话虽如此,使用lambda而不是方法组不太可能对您的应用程序产生可衡量的影响。不过只有测量才能确定。 - dahlbyk
7
Lambda表达式生成更多的代码,但根据这篇文章,Lambda表达式实际上比方法组稍微快一些,并且使用更少的内存,因为“方法组版本每次运行时都会分配一个新对象,而Lambda版本使用一个实例(或静态,必要时)字段来缓存委托。” - Phil Seeman
C#11开始,使用方法组将创建一个委托对象的新实例并重用它,因此不再有额外的不必要分配。 - tia

1

当你的Method1<Guid, BECustomer>接受一个Func<Guid, BECustomer>参数时,Func<Guid, BECustomer>是与以下同义词:

public delegate BECustomer Func(Guid arg);

事实上,所有的Func都只是一个通用的委托:
public delegate TResult Func<T, TResult>(T arg);

编译器可以分析您的代码,并确定您的 Func<Guid, BECustomer>Facade.Customers.GetSingle 的方法组兼容,因为方法签名与委托签名匹配。
这是语法糖的一种形式,也是编译器为您完成繁重工作的又一个例子。

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