.NET Framework中的lambda和委托之间有什么区别?


86

我经常被问到这个问题,我想就如何最好地描述两者之间的差异征求一些意见。


2
“委托”是指委托类型,还是匿名委托?它们也不同。
克里斯·阿默曼


1
为什么人们使他的问题变得如此复杂?只需回答什么是委托人,什么是lambda。给尽可能多的解释,让他选择适合自己的事物。
Imir Hoxha,

Answers:


96

它们实际上是两件事。“ Delegate”实际上是持有对方法或lambda的引用的变量的名称,而lambda是没有永久名称的方法。

Lambda与其他方法非常相似,只是有一些细微的差异。

  1. 普通方法在“语句”中定义,并与一个永久名称相关联,而lambda在“表达式”中“即时”定义,并且没有永久名称。
  2. 某些lambda可以与.NET表达式树一起使用,而方法则不能。

委托的定义如下:

delegate Int32 BinaryIntOp(Int32 x, Int32 y);

只要签名是相同的,则BinaryIntOp类型的变量可以分配一个方法或labmda:两个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#没有“一流的功能”。您可以使用分配给委托变量的函数名称来实质上创建表示该函数的对象。但这实际上是一个编译器技巧。如果通过在函数名称后加上点(例如尝试对函数本身进行成员访问)来编写语句,则会发现那里没有要引用的成员。甚至没有来自对象的。这样可以防止程序员做有用的(当然是潜在的危险)事情,例如添加可以在任何函数上调用的扩展方法。您可以做的最好的事情就是扩展Delegate类本身,这当然也很有用,但还不多。

更新:另请参阅Karg的答案,答案说明了匿名委托与方法和lambda之间的区别。

更新2:James Hart提出了一个重要的建议,尽管非常技术性,但它指出lambda和委托不是.NET实体(即CLR没有委托或lambda的概念),而是它们是框架和语言构造。


很好的解释。尽管我认为您的意思是“一流的功能”,而不是“一流的对象”。:)
ibz

1
你是对的。在编写过程中,我使句子的结构有所不同(“ C#函数实际上不是一流的对象”),却忘了更改它。谢谢!
克里斯·阿默曼

普通方法在“语句”中定义。语句是命令式程序序列中的一个动作,可能基于表达式。方法定义不是一个不同的语法结构吗?在docs.microsoft.com/zh-cn/dotnet/csharp/tour-of-csharp/中
Max Barraclough

32

这个问题有点模棱两可,这解释了您得到的答案之间的巨大差异。

您实际上问过.NET框架中的lambda和委托之间有什么区别;那可能是许多事情之一。您在问:

  • Lambda表达式和C#(或VB.NET)语言中的匿名委托之间有什么区别?

  • .NET 3.5中的System.Linq.Expressions.LambdaExpression对象和System.Delegate对象之间有什么区别?

  • 或介于这些极端之间或周围的某个地方?

有些人似乎试图为您回答“ C#Lambda表达式与.NET System.Delegate之间的区别是什么”的问题,这没有什么意义。

.NET框架本身并不理解匿名委托,lambda表达式或闭包的概念,这些都是语言规范定义的。考虑一下C#编译器如何将匿名方法的定义转换为生成的类上带有成员变量的方法,该成员变量具有关闭状态;到.NET,委托没有任何匿名性。对于编写它的C#程序员而言,它只是匿名的。分配给委托类型的lambda表达式也是如此。

什么.NET DOES理解为代表的想法-它描述的方法签名类型,它们的实例代表或者绑定到特定对象,或者绑定特定的调用方法的调用特定方法上,可以对被调用特定类型所述方法遵循所述签名的任何类型的对象。这些类型都继承自System.Delegate。

.NET 3.5还引入了System.Linq.Expressions命名空间,该命名空间包含用于描述代码表达式的类,因此也可以表示对特定类型或对象的方法的绑定或未绑定调用。然后,可以将LambdaExpression实例编译为实际的委托(从而对基于表达式结构的动态方法进行代码生成,并返回指向它的委托指针)。

在C#中,您可以通过将lambda表达式分配给该类型的变量来生成System.Expressions.Expression类型的实例,这将产生适当的代码以在运行时构造该表达式。

当然,如果你问有什么区别lambda表达式和C#匿名方法之间,毕竟,那么这一切是非常irelevant,在这种情况下,主要的区别是简洁,当你穿上这对匿名委托斜靠”不必在意参数,也不要计划返回值,当您要输入推断的参数和返回类型时,请不要使用lambda。

lambda表达式支持表达式生成。


3
好信息!您启发了我启动反射器并查看IL。我不知道lambda会生成生成的类,但是现在考虑一下,这是完全合理的。
克里斯·阿默曼

20

一个区别是,匿名委托可以省略参数,而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;
}

13

委托等效于函数指针/方法指针/回调(随您选择),而lambda是几乎简化的匿名函数。至少那是我告诉人们的。


究竟!没有区别”。它们是两个本质上不同的事物。
ibz

3

我对此没有很多经验,但是我要描述的方式是委托是任何函数的包装,而lambda表达式本身就是一个匿名函数。


3

委托基本上基本上只是一个函数指针。Lambda可以变成委托,但也可以变成LINQ表达式树。例如,

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

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


2
没错,但是它们之间的区别在于它们是两个完全不同的概念。这就像比较苹果和桔子。请参阅Dan Shield的答案。
ibz

2

lambda只是委托上的语法糖。编译器最终将lambda转换为委托。

我相信这些都是一样的:

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

2
这些示例都无法编译。即使您Delegate从'delegate'更改实例的名称,这也是一个关键字。
史蒂夫·库珀

2

委托是函数签名;就像是

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的变量时,实际上并不是它的运行时类型。运行时类型为委托。委托,lambda和表达式树的编译方式涉及编译器技巧,我认为这会导致代码隐含不正确的内容。
克里斯·阿默曼

2

委托是对具有特定参数列表和返回类型的方法的引用。它可能包含对象,也可能不包含对象。

Lambda表达式是匿名函数的一种形式。


2

委托是函数指针的队列,调用委托可以调用多个方法。Lambda本质上是一个匿名方法声明,根据其使用的上下文,编译器可能会不同地解释它。

您可以通过将lambda表达式转换为委托来获得指向lambda表达式的委托,或者如果将其作为参数传递给需要特定委托类型的方法,则编译器将为您转换该委托。在LINQ语句中使用它,lambda将由编译器转换为表达式树,而不是简单的委托。

区别实际上是,lambda是在另一个表达式内定义方法的简洁方法,而委托是实际的对象类型。



1

代表实际上只是函数的结构化类型。您可以使用名义上的类型输入和实现实现接口或抽象类的匿名类来执行相同的操作,但是当只需要一个函数时,最终将产生大量代码。

Lambda来自1930年代Alonzo Church的lambda演算的思想。这是创建函数的匿名方式。它们对于组成函数特别有用

因此,尽管有人可能会说lambda是代表的语法糖,但我会说代表是在c#中使人们轻松使用lambda的桥梁。


1

一些基本的。“委托”实际上是持有对方法或lambda的引用的变量的名称

这是一个匿名方法-

(string testString) => { Console.WriteLine(testString); };

由于匿名方法没有任何名称,因此我们需要一个可以在其中分配这两种方法或表达式的委托。对于前

delegate void PrintTestString(string testString); // declare a delegate

PrintTestString print = (string testString) => { Console.WriteLine(testString); }; 
print();

与lambda表达式相同。通常我们需要委托才能使用它们

s => s.Age > someValue && s.Age < someValue    // will return true/false

我们可以使用func委托来使用此表达式。

Func< Student,bool> checkStudentAge = s => s.Age > someValue && s.Age < someValue ;

bool result = checkStudentAge ( Student Object);

0

Lambda是代表的简化版本。它们具有闭包的某些属性,例如匿名委托,但是还允许您使用隐式键入。这样的lambda:

something.Sort((x, y) => return x.CompareTo(y));

比您可以对委托执行的操作更为简洁:

something.Sort(sortMethod);
...

private int sortMethod(SomeType one, SomeType two)
{
    one.CompareTo(two)
}

您的意思是lambda就像简化的匿名方法(不是委托)。与方法(匿名或非匿名)一样,可以将它们分配给委托变量。
卢卡斯

0

这是我在la脚的博客上发表过的例子。假设您要通过工作线程更新标签。我有4个示例,说明如何使用委托,匿名委托和2种类型的lambda将标签从1更新为50。

 private void button2_Click(object sender, EventArgs e) 
     { 
         BackgroundWorker worker = new BackgroundWorker(); 
         worker.DoWork += new DoWorkEventHandler(worker_DoWork); 
         worker.RunWorkerAsync(); 
     } 

     private delegate void UpdateProgDelegate(int count); 
     private void UpdateText(int count) 
     { 
         if (this.lblTest.InvokeRequired) 
         { 
             UpdateProgDelegate updateCallBack = new UpdateProgDelegate(UpdateText); 
             this.Invoke(updateCallBack, new object[] { count }); 
         } 
         else 
         { 
             lblTest.Text = count.ToString(); 
         } 
     } 

     void worker_DoWork(object sender, DoWorkEventArgs e) 
     {   
         /* Old Skool delegate usage.  See above for delegate and method definitions */ 
         for (int i = 0; i < 50; i++) 
         { 
             UpdateText(i); 
             Thread.Sleep(50); 
         } 

         // Anonymous Method 
         for (int i = 0; i < 50; i++) 
         { 
             lblTest.Invoke((MethodInvoker)(delegate() 
             { 
                 lblTest.Text = i.ToString(); 
             })); 
             Thread.Sleep(50); 
         } 

         /* Lambda using the new Func delegate. This lets us take in an int and 
          * return a string.  The last parameter is the return type. so 
          * So Func<int, string, double> would take in an int and a string 
          * and return a double.  count is our int parameter.*/ 
         Func<int, string> UpdateProgress = (count) => lblTest.Text = count.ToString(); 
         for (int i = 0; i < 50; i++) 
         { 
             lblTest.Invoke(UpdateProgress, i); 
             Thread.Sleep(50); 
         } 

         /* Finally we have a totally inline Lambda using the Action delegate 
          * Action is more or less the same as Func but it returns void. We could 
          * use it with parameters if we wanted to like this: 
          * Action<string> UpdateProgress = (count) => lblT…*/ 
         for (int i = 0; i < 50; i++) 
         { 
             lblTest.Invoke((Action)(() => lblTest.Text = i.ToString())); 
             Thread.Sleep(50); 
         } 
     }

0

我假设您的问题与c#有关,而不与.NET有关,因为您的问题含糊不清,因为.NET并不孤单-也就是说,没有c#-理解委托和lambda表达式。

一个(正常的,与所谓的泛型委托相反,后来称为cf)委托应该被视为一种typedef函数指针类型的c ++,例如在c ++中:

R (*thefunctionpointer) ( T ) ;

typedef的类型thefunctionpointer是指向函数的指针的类型,该函数采用typeT的对象并返回type的对象R。您可以这样使用它:

thefunctionpointer = &thefunction ;
R r = (*thefunctionpointer) ( t ) ; // where t is of type T

thefunction一个将aT并返回a的函数在哪里R

在C#中,您会

delegate R thedelegate( T t ) ; // and yes, here the identifier t is needed

你会这样使用它:

thedelegate thedel = thefunction ;
R r = thedel ( t ) ; // where t is of type T

thefunction一个将aT并返回a的函数在哪里R?这是给代表的,即所谓的普通代表。

现在,您在c#中还具有泛型委托,这些泛型委托是泛型的委托,即可以说是“模板化”的委托,因此使用c ++表达式。它们的定义如下:

public delegate TResult Func<in T, out TResult>(T arg);

您可以像这样使用它们:

Func<double, double> thefunctor = thefunction2; // call it a functor because it is
                                                // really as a functor that you should
                                                // "see" it
double y = thefunctor(2.0);

thefunction2函数以哪里为参数并返回double

现在,想象一下,不是thefunction2我想使用一个现在尚未在声明中定义的“函数”,而是以后将不再使用。然后,c#允许我们使用此函数的表达式。通过表达我的意思了“数学”(或功能性,坚持程序)它的表达,例如:到double x我会相关联double x*x。在数学中,您可以使用“ \ mapsto”乳胶符号编写此代码。在c#中借用了功能符号:=>。例如 :

Func<double, double> thefunctor = ( (double x) => x * x ); // outer brackets are not
                                                           // mandatory

(double x) => x * x表达。它不是类型,而代理(泛型或非泛型)则是。

道德?最后,如果不是函数指针类型(resp.wrapped + smart + generic函数指针类型),那么委托是什么(分别是通用委托),是吗?还有别的!看到这个那个


-1

好吧,真正简化的版本是lambda只是匿名函数的简写。委托不仅可以执行匿名功能,还可以做很多事情:事件,异步调用和多个方法链。


1
lambda可用作事件处理程序;button.Click + =(sender,eventArgs)=> {MessageBox.Show(“ Click”); },并异步调用新的System.Threading.Thread(()=> Console.Write(“在线程上执行”))。Start();
史蒂夫·库珀
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.