在.NET Framework中,Lambda和Delegate有什么区别?

110

我经常被问及这个问题,我想征求一些关于如何最好地描述它们之间区别的意见。


2
“Delegates” 指的是委托类型还是匿名委托?它们也是不同的。 - Chris Ammerman
1
为什么人们把问题搞得这么复杂?只需回答什么是委托和什么是lambda。尽可能多地解释,让他自己选择适合他的东西。 - Imir Hoxha
18个回答

119
他们实际上是两个非常不同的东西。 "Delegate" 实际上是一个变量的名称,它保存对方法或 lambda 的引用,而 lambda 是一种没有永久名称的方法。
Lambda 非常像其他方法,只有一些微妙的差别。
  1. 普通方法在 "语句(statement)" 中被定义并与永久名称相关联,而 lambda 在 "表达式(expression)" 中“即时”定义,没有永久名称。
  2. 一些 lambdas 可以与 .NET 表达式树一起使用,而方法则不能。
委托的定义如下:
delegate Int32 BinaryIntOp(Int32 x, Int32 y);

BinaryIntOp类型的变量可以分配方法或lambda,只要签名相同:两个Int32参数和一个Int32返回值。

可以这样定义lambda:

BinaryIntOp sumOfSquares = (a, b) => a*a + b*b;

另一个需要注意的是,尽管通用的 Func 和 Action 类型通常被认为是“lambda 类型”,但它们就像任何其他委托类型一样。 它们的好处在于,它们本质上定义了您可能需要的任何类型的委托名称(最多4个参数,虽然您可以自己添加更多)。 因此,如果您正在使用各种委托类型,但没有重复使用,您可以通过使用 Func 和 Action 避免在代码中混杂委托声明。
下面是 Func 和 Action “不仅仅适用于 lambda”的说明:
Int32 DiffOfSquares(Int32 x, Int32 y)
{
  return x*x - y*y;
}

Func<Int32, Int32, Int32> funcPtr = DiffOfSquares;

另外一个有用的知识是,具有相同签名但不同名称的委托类型(而不是方法本身)将不会被隐式地转换为彼此。这包括Func和Action委托。但是,如果签名完全相同,您可以在它们之间显式地进行转换。
更进一步……在C#中,函数是灵活的,使用lambda和委托。但是C#没有“一等公民函数”。您可以使用分配给委托变量的函数名称来创建代表该函数的对象。但这确实是编译器的技巧。如果您开始通过写函数名称后跟一个点(即尝试对函数本身进行成员访问)来编写语句,您会发现那里没有成员可供引用。甚至没有来自Object的成员。这可以防止程序员执行有用的(当然也可能很危险)操作,例如添加可以在任何函数上调用的扩展方法。您能做的最好的事情就是扩展Delegate类本身,这肯定也很有用,但不是那么重要。
更新:还请参见Karg's answer,其中说明了匿名委托与方法& lambda之间的区别。
更新2:James Hart做出了一个非常技术性的重要说明,即lambda和委托不是.NET实体(即CLR没有委托或lambda的概念),而是框架和语言构造。

很好的解释。虽然我认为你的意思是“一等函数”,而不是“一等对象”。 :) - ibz
1
你说得对。在写作过程中,我把句子结构弄错了(“C# 函数实际上不是一等对象”),忘记改正了。谢谢! - Chris Ammerman
一个普通的方法在“语句”中被定义。语句是命令式程序序列中的一个动作,可能基于一个表达式。方法定义不是不同的语法结构吗?方法定义没有在https://learn.microsoft.com/en-us/dotnet/csharp/tour-of-csharp/statements中列出。 - Max Barraclough
实际上,在微软的文档中指出,Lambda 表达式是委托。“再次强调,Lambda 表达式只是委托的一种形式,这意味着它们可以作为事件处理程序使用而不会引起任何问题,正如下面的代码片段所示。” - onmi

36
这个问题有点模糊,这就解释了你得到的答案差异很大。实际上,你问的是.NET框架中Lambda和Delegate之间的区别;这可能是许多问题中的一个。你是在问:
- C#(或VB.NET)语言中Lambda表达式和匿名委托之间的区别是什么? - .NET 3.5中System.Linq.Expressions.LambdaExpression对象和System.Delegate对象之间的区别是什么? - 或者介于或围绕这些极端之间的某些事情?
一些人似乎试图回答“C# Lambda表达式和.NET System.Delegate之间的区别是什么?”,这没有太多意义。
.NET框架本身并不理解匿名委托、Lambda表达式或闭包的概念——这些都是由语言规范定义的。想想C#编译器如何将匿名方法的定义转换为生成类上的方法,该类具有成员变量以保存闭包状态;对于.NET来说,委托没有任何匿名性;对于编写它的C#程序员来说,它只是匿名的。对于分配给委托类型的Lambda表达式同样适用。
.NET理解的是委托的概念——描述方法签名的类型,它的实例表示绑定到特定对象上的特定方法的调用,或者表示对特定类型上的特定方法的未绑定调用,该方法可以针对该类型的任何对象进行调用,其中所述方法符合所述签名。这样的类型都继承自System.Delegate。
.NET 3.5还引入了System.Linq.Expressions命名空间,其中包含用于描述代码表达式的类——因此也可以表示特定类型或对象上的绑定或未绑定调用。然后可以将LambdaExpression实例编译为实际的委托(通过基于表达式结构的动态方法生成代理指针并返回)。
在C#中,你可以通过将Lambda表达式分配给该类型的变量来生成System.Expressions.Expression类型的实例,这将在运行时产生构建表达式所需的适当代码。
当然,如果你最终是在问C#中Lambda表达式和匿名方法之间的区别,那么所有这些都几乎无关紧要,在这种情况下,主要区别在于简洁性,当你不关心参数并且不打算返回值时,它倾向于匿名委托,而当你想要推断类型的参数和返回类型时,它倾向于Lambda。Lambda表达式支持表达式生成。

3
很棒的信息!你激励我启动反编译器并查看IL代码。我不知道Lambda表达式会生成类,但现在想起来就很合理了。 - Chris Ammerman

27

一个不同之处是匿名委托可以省略参数,而Lambda表达式必须匹配精确的签名。例如:

public delegate string TestDelegate(int i);

public void Test(TestDelegate d)
{}

你可以以下四种方式调用它(请注意,第二行有一个匿名委托,没有任何参数):

Test(delegate(int i) { return String.Empty; });
Test(delegate { return String.Empty; });
Test(i => String.Empty);
Test(D);

private string D(int i)
{
    return String.Empty;
}

您不能传递没有参数的 Lambda 表达式或没有参数的方法。这些是不允许的:

Test(() => String.Empty); //Not allowed, lambda must match signature
Test(D2); //Not allowed, method must match signature

private string D2()
{
    return String.Empty;
}

16

委托类似于函数指针/方法指针/回调函数(自己选择),而lambda表达式则是简化的匿名函数。至少这就是我告诉人们的。


1
没错!它们之间没有“区别”。它们是两个本质不同的东西。 - ibz

4
代表是一个函数签名;类似于
delegate string MyDelegate(int param1);

委托没有实现主体。

lambda是一个函数调用,其签名与委托相匹配。对于上述委托,您可以使用以下任何一个:

(int i) => i.ToString();
(int i) => "ignored i";
(int i) => "Step " + i.ToString() + " of 10";
Delegate 类型的名称不太准确,实际上创建一个 Delegate 类型的对象会创建一个可以保存函数(无论是 lambda、静态方法还是类方法)的变量。

当你创建类型为MyDelegate的变量时,它的运行时类型并不是真正的MyDelegate类型,而是Delegate类型。委托、Lambda表达式和表达式树编译过程中涉及到编译器技巧,这可能会导致代码暗示一些并非真实情况的事情。 - Chris Ammerman

3

委托本质上就是一个函数指针。Lambda表达式可以转换为委托,也可以转换为LINQ表达式树。例如,

Func<int, int> f = x => x + 1;
Expression<Func<int, int>> exprTree = x => x + 1;

第一行产生一个委托,第二行产生一个表达式树。

2
没错,但是它们之间的区别在于,它们是两个完全不同的概念。这就像比较苹果和橙子一样。请查看丹·希尔德的答案。 - ibz

3

虽然我在这方面的经验不是很丰富,但我认为委托是对任何函数的封装,而Lambda表达式本身就是一个匿名函数。


3

简短版:

委托是代表方法引用的类型。C# lambda 表达式是创建 委托表达式树 的语法。

稍微详细版:

委托不是如接受的答案所说的"变量的名称"。

委托是一个类型(实际上是一个类型,如果您检查 IL,则是类),它代表对方法的引用 (learn.microsoft.com)。

enter image description here

可以初始化此类型以将其实例与具有兼容签名和返回类型的任何方法关联。

namespace System
{
    // define a type
    public delegate TResult Func<in T, out TResult>(T arg);
}

// method with the compatible signature
public static bool IsPositive(int int32)
{
    return int32 > 0;
}

// initiated and associate
Func<int, bool> isPositive = new Func<int, bool>(IsPositive);

C# 2.0引入了一种语法糖——匿名方法,允许在内联定义方法。
Func<int, bool> isPositive = delegate(int int32)
{
    return int32 > 0;
};

在 C# 3.0+ 中,上述匿名方法的内联定义可以使用 lambda 表达式进一步简化。
Func<int, bool> isPositive = (int int32) =>
{
    return int32 > 0;
};

C#的lambda表达式是一种创建委托表达式树的语法。我认为表达式树不是这个问题的重点(Jamie King关于表达式树的视频)。
更多内容请点击这里

2

lambda表达式只是委托的语法糖。编译器最终会将lambda表达式转换为委托。

我相信它们是相同的:

Delegate delegate = x => "hi!";
Delegate delegate = delegate(object x) { return "hi";};

2
这两个示例都无法编译。即使您更改“Delegate”的实例名称(该名称是关键字)。 - Steve Cooper

2
很明显,这个问题的意思应该是“lambda表达式和匿名委托之间有什么区别?”在所有答案中,只有一个人回答正确——主要的区别是lambda表达式既可以用于创建委托,也可以用于创建表达式树。
你可以在MSDN上阅读更多信息:http://msdn.microsoft.com/en-us/library/bb397687.aspx

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