理解Lambda表达式和委托

15

我已经尝试了相当长的一段时间(阅读在线博客和文章),但到目前为止都没有成功弄清楚。

委托(delegates)是什么?Lambda表达式又是什么?它们各自的优缺点是什么?使用其中一个还是另一个的可能最佳实践是什么?

提前感谢您。


2
Randolpho,这不是重复的问题。我正在寻找对Lambda表达式和委托的基本理解。 - user279521
6
@Randolpho:这里不一定是重复问题。我认为OP问题的最大部分在于询问“它们”是什么,而你所提到的问题只关注它们之间的区别。 - David Hedlund
虽然不是重复的问题,但这是一个相当广泛的问题。Lambda表达式和“委托”并不是非常相关的。更好的方法是将它们分成两个问题。请参见c-sharp-lambda-expression-why-should-i-use-thisdelegates-in-c-sharp - nawfal
还有这个这个这个等等一些类似的比较。或者也可以看看这个综合性的问题 - nawfal
6个回答

30
委托(Delegates)是可以像字符串一样用作变量的方法。例如,您可以声明具有一个参数的委托方法:
delegate void OneArgumentDelegate(string argument);
它本身什么也不做,就像接口一样。如果您在任何类中有一个具有这样一个参数的方法:
void SomeMethod(string someArgument) {}
它与该委托的签名匹配,因此可以将其分配给其类型的变量:
OneArgumentDelegate ThisIsAVariable = new OneArgumentDelegate(SomeMethod);
OneArgumentDelegate ThisIsAlsoAVariable = SomeMethod; // 简写也可以
这些可以作为参数传递给方法并被调用,如下所示:
void Main()
{
  DoStuff(PrintString);
}
void PrintString(string text) { Console.WriteLine(text); }
void DoStuff(OneArgumentDelegate action) { action("Hello!"); }
这将输出Hello!
Lambda表达式是DoStuff(PrintString)的简写,因此您不必为要使用的每个委托变量创建一个方法。您'创建'一个传递给方法的临时方法。它的工作方式如下:
DoStuff(string text => Console.WriteLine(text)); // 单行
DoStuff(string text => // 多行
{
  Console.WriteLine(text);
  Console.WriteLine(text);
});
Lambda表达式只是一种简写,您也可以创建单独的方法并将其传递。希望您现在更好地理解它了;-)

1
在大多数情况下,在lambda表达式中声明输入变量的类型是不必要的,而且在我看来,这会使代码变得冗长(而lambda表达式本来就是试图解决这个问题的)。DoStuff(txt => Console.WriteLine(txt))同样足够。 - Chris
1
+1一个不错的解释 - 肯定比我在网上看到的大多数解释要好。 - CJM
3
@Chris 我本来考虑删掉这一部分,但为了清晰起见,我决定不删。如果删掉这里可能会让不熟悉语法的人感到困惑。但在“真实”的代码中,我通常像你建议的那样省略它。 - Jouke van der Maas
1
说 lambda 是 DoStuff(PrintString) 的简写可能在选择示例时不太幸运。请考虑 DoStuff(text => Console.WriteLine(text)); 只是 DoStuff(Console.WriteLine); 更冗长的写法,因为你的 lambda 与 Console.WriteLine 具有完全相同的签名:它们都是一个接受字符串并返回 void 的方法。 - David Hedlund
1
@David 当然你是对的,但我不想在这里使用任何复杂的例子。肯定可以做得更好,但为了这个目的,我认为这样就够了。 - Jouke van der Maas
为了让lambda表达式工作(在VS 2017中),我需要删除string,正如@DavidHedlund的评论所述... - SlowLearner

7
Delegate是一个持有函数引用的对象。多个不同的代理可以指向同一个函数。一个委托的类型定义了它可以指向的函数的脚印。 Lambda表达式是一个没有名称的函数。执行这个函数的唯一方法是拥有一个指向该函数的委托。通常在需要指向具有给定脚印的函数的委托的地方定义Lambda表达式。这有助于使代码更简洁,同时更具描述性和灵活性。
我建议您在某些要从不同位置调用的代码时使用具有名称的函数及其委托。一个常见的例子是您想要附加到几个事件生产者的事件监听器。
另一个编写单独函数的要点是代码的复杂性。如果您在Lambda表达式中编写整个程序,那么对任何人都没有帮助。
另一方面,您经常需要一些微不足道的处理方式,您希望以回调方式执行。这是您可能会喜欢Lambda表达式的地方。
Lambda表达式非常好的一点是它们继承了它们被定义的作用域,因此您可以轻松地在Lambda表达式中使用变量,并因此传递大量信息。不过,您应该小心,参见文章的备注部分。
在LINQ中,Lambda与表达式结合使用非常出色。
最后,我必须引用另一个必读的msdn部分:
当您使用基于方法的语法调用Enumerable类中的Where方法时(如在LINQ to Objects和LINQ to XML中所做的那样),参数是委托类型System.Func。Lambda表达式是创建该委托的最方便的方法。当您在例如System.Linq.Queryable类中调用相同的方法(如在LINQ to SQL中所做的那样)时,参数类型是System.Linq.Expressions.Expression,其中Func是任何具有多达16个输入参数的Func委托。同样,Lambda表达式只是构造该表达式树的一种非常简洁的方式。虽然实际上从Lambda创建的对象类型不同,但这些lambda使得Where调用看起来相似。

谢谢提供信息。您能举例说明何时使用委托和何时使用Lambda表达式吗? - user279521

5

没有人提到匿名委托。您可以在不声明委托的情况下即时创建委托:

public void Action(Func<int, int> func);
...
Action(delegate(int x) { return x*x; });

这只是lambda语法更加冗长的一种形式:

Action(x => x*x);

注意,lambda语法具有更激进的类型推断。另一个区别是,lambda符号可以用于声明表达式树:

public void Action(Expression<Func<int, int>>);
Action(x => x*x);

在某些情况下,你得到的不是一个函数,而是一棵解析树,你可以在运行时检查它。这就是linq查询构建它们的sql的方式,例如。
编辑:
更直接地回答何时使用其中之一的问题:
你很少需要自己声明新的委托类型,尽管有时会有帮助。框架提供了几种Func<>类型,以及通常所需的Action和Predicate。
在“即兴创建”函数时,使用匿名委托语法而不是lambda语法没有任何优势。由于lambda语法更简洁且类型推断,因此应优先使用它。

在使用LINQ to SQL时,您不需要理解表达式树,但它们是语言的重要特性,如果提到表达式树将会得到加分。此外,在C# 3.0中添加Lambda表达式之前,获取类似Lambda的语义的唯一方法是使用匿名委托。我记得在C# 2.0代码中经常这样做。 - Daniel Pryden
对于编程方面的内容,我个人更喜欢使用匿名委托语法而不是lambda表达式 +1。 - UpTheCreek

2

在使用lambda表达式和委托时,有一个重要的区别。

private delegate int emptymethoddel();
// Delegate for method with no params and returns int

相应的框架委托类型是:Func<int>

但是你无法从参数化方法创建新的委托实例/函数。

private int TwoArgMethod(int i, int j)
{
    return i + j;
}

但是,使用 lambda,您可以获得上述方法的委托。

Func<int> retmethod = () => TwoArgMethod(10, 20);

但是对于委托实例化,我们不能像下面这样做:

emptymethoddel retmethod4 = new emptymethoddel(TwoArgMethod(10,20)); 
// mismatch method signature

通过lambda,我们可以获得指向不匹配“Func”或任何其他变体的方法的指针。


2

委托就是函数指针。它就像一个“变量”,可以保存另一个将被调用的函数的地址。

    public class test {
    Action<int> CallUserCode;

    public test(Action<int> proc){
        CallUserCode = proc;
    }

    void foo(){
        int someValue = 0;
        //do some stuff that needs to call the user procedure
        CallUserCode(someValue);
    }
}

Lambda表达式也是一个委托,它具有简化的语法,可以“内联”创建函数。因此,可以使用lambda表达式来调用上述示例,如下所示。
void bar(){
    var t = new test(x => { /* do something with the value i get from foo */});
    t.foo();  //here function foo gets called, which will call 'do something' AND call my lambda expression
}

1

正如其他人所说,lambda是一种用于内联和匿名创建委托的语法。使用lambda可以做到传统函数无法实现的闭包功能。这样,您就可以在运行时使用运行时信息创建函数:

string mystring = SomeObject.GetMyString();
AnotherObject.OnSomeEvent += (eventparams => 
{
  string newstring = string.Format(eventparams.Message, mystring);
  SomeService.PrintEvent(newstring);
}

这样,mystring 就被合并到委托中,可以作为一个变量使用。


1
您可以创建匿名委托,它们是闭包。 - Gabe Moothart

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