C#Lambda表达式:为什么要使用它们?


309

我已经快速阅读了Microsoft Lambda Expression文档。

但是,这种示例有助于我更好地理解:

delegate int del(int i);
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25

但是,我仍然不明白为什么它是如此创新。这只是“方法变量”结束时消失的方法,对吗?为什么要使用此方法而不是实际方法?


3
对于那些你们谁来到这个页面,不知道什么delegate是在C#中,我强烈建议阅读此之前阅读本页面的其余部分:stackoverflow.com/questions/2082615/...
科罗布峡谷

Answers:


281

Lambda表达式是匿名委托的一种较简单的语法,可以在可以使用匿名委托的任何地方使用。但是,事实并非如此。可以将lambda表达式转换为表达式树,这使LINQ to SQL有了很多魔力。

下面是一个使用匿名委托然后使用lambda表达式的LINQ to Objects表达式的示例,以显示它们在眼睛上有多容易:

// anonymous delegate
var evens = Enumerable
                .Range(1, 100)
                .Where(delegate(int x) { return (x % 2) == 0; })
                .ToList();

// lambda expression
var evens = Enumerable
                .Range(1, 100)
                .Where(x => (x % 2) == 0)
                .ToList();

Lambda表达式和匿名委托比编写单独的函数有一个优势:它们实现闭包,可以使您将局部状态传递给函数,而无需向函数添加参数或创建一次性对象。

表达式树是C#3.0的一项非常强大的新功能,它使API可以查看表达式的结构,而不仅仅是获取对可以执行的方法的引用。API只需将委托参数变成Expression<T>参数,编译器将通过lambda而不是匿名委托生成表达式树:

void Example(Predicate<int> aDelegate);

像这样

Example(x => x > 5);

变成:

void Example(Expression<Predicate<int>> expressionTree);

后者将传递描述该表达式的抽象语法树的表示形式x > 5。LINQ to SQL依靠此行为,能够将C#表达式转换为服务器端进行过滤/排序等所需的SQL表达式。


1
如果没有闭包,则可以将静态方法用作回调,但是您仍然必须在某些类中定义这些方法,几乎​​是在一定程度上扩大了该方法的范围,超出了预期的用途。
DK。

10
FWIW,您可以使用匿名委托进行闭包,因此您并不需要严格地使用lambda。Lambda的知名度仅比匿名代表可读性强,否则不使用Linq会让您流血。
Benjol,2009年

138

匿名函数和表达式对于一次性方法很有用,这些方法无法从创建完整方法所需的额外工作中受益。

考虑以下示例:

 string person = people.Find(person => person.Contains("Joe"));

 public string FindPerson(string nameContains, List<string> persons)
 {
     foreach (string person in persons)
         if (person.Contains(nameContains))
             return person;
     return null;
 }

这些在功能上是等效的。


8
如何定义Find()方法来处理此lambda表达式?
Patrick Desjardins,

3
Predicate <T>是Find方法所期望的。
达伦·科普

1
由于我的lambda表达式与Predicate <T>的协定匹配,因此Find()方法接受它。
约瑟夫·戴格尔,

您的意思是“字符串person = people.Find(persons => people.Contains(“ Joe”));“
Gern Blanston 09年

5
@FKCoder,不,他没有,尽管如果他说“ string person = people.Find(p => p.Contains(“ Joe”));”可能会更清楚。
杰尔

84

当我想使用另一个控件声明某个控件事件的处理程序时,我发现它们很有用。为此,通常必须将控件的引用存储在类的字段中,以便可以将其用于与创建方法不同的方法中。

private ComboBox combo;
private Label label;

public CreateControls()
{
    combo = new ComboBox();
    label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += new EventHandler(combo_SelectedIndexChanged);
}

void combo_SelectedIndexChanged(object sender, EventArgs e)
{
    label.Text = combo.SelectedValue;
}

感谢lambda表达式,您可以像这样使用它:

public CreateControls()
{
    ComboBox combo = new ComboBox();
    Label label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += (s, e) => {label.Text = combo.SelectedValue;};
}

容易得多。


在第一个示例中,为什么不转换发件人并获取值?
安德鲁(Andrew)

@Andrew:在这个简单的示例中,不必使用发件人,因为只有一个组件有问题,并且使用该字段直接保存了强制类型转换,从而提高了清晰度。在现实世界中,我个人也更愿意使用发送者。通常,如果可能,我将一个事件处理程序用于多个事件,因此我必须确定实际的发送者。
克里斯·托普斯基

35

Lambda清理了C#2.0的匿名委托语法...例如

Strings.Find(s => s == "hello");

像这样在C#2.0中完成:

Strings.Find(delegate(String s) { return s == "hello"; });

从功能上讲,它们执行完全相同的操作,只是语法更加简洁。


3
它们不是完全一样的-正如@Neil Williams指出的那样,您可以使用表达式树提取lambda的AST,而匿名方法不能以相同的方式使用。
ljs 2011年

这是lambda的许多其他优点之一。它比匿名方法有助于更好地理解代码。当然,并不是要创建lambda,而是在这种情况下可以更频繁地使用它。
Guruji '18年

29

这只是使用lambda表达式的一种方式。您可以可以使用委托的任何地方使用lambda表达式。这使您可以执行以下操作:

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

strings.Find(s => s == "hello");

此代码将在列表中搜索与单词“ hello”匹配的条目。执行此操作的另一种方法是将委托实际上传递给Find方法,如下所示:

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

private static bool FindHello(String s)
{
    return s == "hello";
}

strings.Find(FindHello);

编辑

在C#2.0中,可以使用匿名委托语法完成此操作:

  strings.Find(delegate(String s) { return s == "hello"; });

Lambda大大清除了该语法。


2
@Jonathan Holland:感谢您的编辑并添加了匿名委托语法。它很好地完成了示例。
Scott Dorman

什么是匿名委托?//对不起,我是C#的
新手

1
@HackerMan,将匿名委托视为没有“名称”的函数。您仍在定义一个可以具有输入和输出功能的函数,但是由于它是一个名称,因此您不能直接引用它。在上面显示的代码中,您正在定义一个方法(采用a string并返回bool)作为该Find方法本身的参数。
Scott Dorman 2014年

22

微软为我们提供了一种更清洁,更便捷的方式来创建名为Lambda表达式的匿名委托。但是,此语句的表达式部分没有引起太多关注。微软发布了整个命名空间System.Linq.Expressions,其中包含用于基于lambda表达式创建表达式树的类。表达式树由代表逻辑的对象组成。例如,x = y + z是一个表达式,它可能是.Net中表达式树的一部分。考虑以下(简单)示例:

using System;
using System.Linq;
using System.Linq.Expressions;


namespace ExpressionTreeThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<int, int>> expr = (x) => x + 1; //this is not a delegate, but an object
            var del = expr.Compile(); //compiles the object to a CLR delegate, at runtime
            Console.WriteLine(del(5)); //we are just invoking a delegate at this point
            Console.ReadKey();
        }
    }
}

这个例子很简单。而且我确信您在想:“这没用,因为我可以直接创建委托,而不是在运行时创建表达式并进行编译”。你会是对的。但这为表达式树提供了基础。表达式名称空间中有许多表达式,您可以构建自己的表达式。我认为您可以看到,当您不确切知道算法在设计或编译时应该使用什么时,这可能会很有用。我在某处看到了一个使用此示例编写科学计算器的示例。您也可以将其用于贝叶斯系统或遗传编程(AI)。在我的职业生涯中,有几次我不得不编写类似于Excel的功能,该功能允许用户输入简单的表达式(加法,减法等)以对可用数据进行操作。在.Net 3.5之前的版本中,我不得不求助于C#外部的某些脚本语言,或者不得不使用反射代码的功能来动态创建.Net代码。现在,我将使用表达式树。


12

不必将方法定义为远离特定的使用位置,而不必在特定的位置仅使用一次。良好的用途是用作排序等通用算法的比较器,然后您可以在其中定义自定义排序函数,以在其中调用排序,而不是进一步强迫您到其他地方查看正在排序的内容。

这并不是真正的创新。LISP具有lambda函数已超过30年。


6

您还可以在编写通用代码以对您的方法进行操作时找到lambda表达式的用法。

例如:泛型函数来计算方法调用所花费的时间。(即Action在这里)

public static long Measure(Action action)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    action();
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

您可以按以下方式使用lambda表达式调用上述方法,

var timeTaken = Measure(() => yourMethod(param));

表达式使您可以从方法中获取返回值以及参数

var timeTaken = Measure(() => returnValue = yourMethod(param, out outParam));

5

Lambda表达式是表示匿名方法的一种简洁方法。匿名方法和Lambda表达式都允许您内联定义方法实现,但是,匿名方法明确要求您定义方法的参数类型和返回类型。Lambda表达式使用C#3.0的类型推断功能,该功能允许编译器根据上下文推断变量的类型。这非常方便,因为这可以节省很多打字时间!


5

Lambda表达式就像代替委托实例编写的匿名方法。

delegate int MyDelagate (int i);
MyDelagate delSquareFunction = x => x * x;

考虑lambda表达式 x => x * x;

输入参数值为x(在=>的左侧)

函数逻辑为x * x(在=>的右侧)

Lambda表达式的代码可以是语句块,而不是表达式。

x => {return x * x;};

注意:Func是预定义的通用委托。

    Console.WriteLine(MyMethod(x => "Hi " + x));

    public static string MyMethod(Func<string, string> strategy)
    {
        return strategy("Lijo").ToString();
    }

参考文献

  1. 委托和接口如何互换使用?


3

这是一种进行小操作并将其非常靠近使用位置的方式(与声明变量接近其使用点的方式不同)。这应该使您的代码更具可读性。通过使表达式匿名化,如果该函数在其他地方使用并被修改为“增强”功能,则使他人很难破坏您的客户端代码。

同样,为什么需要使用foreach?您可以使用简单的for循环或直接使用IEnumerable在foreach中进行所有操作。答:您不需要它,但是它使您的代码更具可读性。


0

创新之处在于类型的安全性和透明度。尽管您没有声明lambda表达式的类型,但它们是被推断的,可以被代码搜索,静态分析,重构工具和运行时反射使用。

例如,在您可能已经使用SQL并可能遭受SQL注入攻击之前,因为黑客传递了一个通常应输入数字的字符串。现在,您将使用一个LINQ lambda表达式,该表达式受到保护。

无法在纯委托人上构建LINQ API,因为在评估它们之前需要将表达式树组合在一起。

在2016年,大多数流行语言都支持lambda表达,而C#是主流命令性语言中这种演变的先驱之一。


0

这也许是关于为什么使用lambda表达式的最佳解释-> https://youtu.be/j9nj5dTo54Q

总之,这是为了提高代码的可读性,通过重用而不是复制代码来减少出错的机会,并利用幕后发生的优化。


0

Lambda表达式和匿名函数的最大好处是,它们允许库/框架的客户端(程序员)通过给定库/框架中的代码注入功能(因为它是LINQ,ASP.NET Core和许多其他方法),而常规方法则无法做到。但是,对于单个应用程序程序员来说,它们的优势并不明显,但是对于创建库的人(那些稍后将要配置库代码的行为的其他人)或使用库的人来说,它们的优势并不明显。因此,有效使用lambda表达式的上下文是库/框架的使用/创建。

同样,由于它们描述了一次性用法代码,因此它们不必成为类的成员,而这将导致更高的代码复杂性。想象一下,每次我们想要配置类对象的操作时,都必须声明一个焦点不明确的类。

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.