【问题标题】:C# Lambda expressions: Why should I use them?C# Lambda 表达式:我为什么要使用它们?
【发布时间】:2008-10-03 15:11:14
【问题描述】:

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

不过,这种例子帮助我更好地理解:

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

不过,我还是不明白为什么这是一项创新。这只是“方法变量”结束时死亡的方法,对吗?为什么要使用这个而不是真正的方法?

【问题讨论】:

标签: c# c#-3.0 lambda


【解决方案1】:

Lambda expressions 是一种更简单的匿名委托语法,可以在任何可以使用匿名委托的地方使用。然而,事实并非如此。 lambda 表达式可以转换为表达式树,这允许许多魔法,如 LINQ to SQL。

以下是 LINQ to Objects 表达式的示例,它使用匿名委托和 lambda 表达式来展示它们的易用性:

// 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 表达式和匿名委托比编写单独的函数有一个优势:它们实现了closures,这可以让您将pass local state to the function without adding parameters 传递给函数或创建一次性使用的对象。

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

void Example(Predicate<int> aDelegate);

像这样称呼:

Example(x => x > 5);

变成:

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

后者将获得描述表达式x &gt; 5abstract syntax tree 的表示。 LINQ to SQL 依靠这种行为能够将 C# 表达式转换为服务器端过滤/排序等所需的 SQL 表达式。

【讨论】:

  • 如果没有闭包,您可以使用静态方法作为回调,但您仍然必须在某些类中定义这些方法,几乎​​肯定会增加此类方法的范围超出预期用途。
  • FWIW,你可以有一个匿名委托的闭包,所以你不需要 lambdas。 Lambda 比匿名委托更易读,没有它,使用 Linq 会让你眼花缭乱。
【解决方案2】:

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

考虑这个例子:

 List<string> people = new List<string> { "name1", "name2", "joe", "another name", "etc" };
 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;
 }

这些在功能上是等效的。

【讨论】:

  • 如何定义 Find() 方法来处理这个 lambda 表达式?
  • Predicate 是 Find 方法所期望的。
  • 由于我的 lambda 表达式匹配 Predicate 的协定,Find() 方法接受它。
  • 你的意思是“string person = people.Find(persons => persons.Contains("Joe"));”
  • @FKCoder,不,他没有,尽管如果他说 "string person = people.Find(p => p.Contains("Joe")); 可能会更清楚。 "
【解决方案3】:

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

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;};
}

容易多了。

【讨论】:

  • 在第一个例子中,为什么不强制发送sender并获取值?
  • @Andrew:在这个简单的例子中,没有必要使用发送者,因为只有一个组件有问题,使用该字段直接保存一个演员,这提高了清晰度。在现实世界的场景中,我个人也更喜欢使用发件人。通常,如果可能,我会为多个事件使用一个事件处理程序,因此我必须确定实际的发送者。
【解决方案4】:

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

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

在 C# 2.0 中是这样完成的:

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

从功能上讲,它们做的事情完全相同,只是语法更简洁。

【讨论】:

  • 它们完全是一回事 - 正如@Neil Williams 指出的那样,您可以使用表达式树提取 lambdas 的 AST,而匿名方法不能相同方式。
  • 这是 lambda 的许多其他优点之一。与匿名方法相比,它有助于更​​好地理解代码。当然这不是创建 lambdas 的意图,但这些是可以更频繁使用的场景。
【解决方案5】:

这只是使用 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 显着清理了该语法。

【讨论】:

  • @Jonathan Holland:感谢您的编辑和添加匿名委托语法。它很好地完成了这个例子。
  • 什么是匿名委托?//对不起,我是 C# 新手
  • @HackerMan,将匿名委托视为没有“名称”的函数。您仍在定义一个可以具有输入和输出的函数,但是由于它是一个名称,因此您不能直接引用它。在上面显示的代码中,您正在定义一个方法(接受 string 并返回 bool)作为 Find 方法本身的参数。
【解决方案6】:

Microsoft 为我们提供了一种更简洁、更方便的方式来创建称为 Lambda 表达式的匿名委托。但是,该语句的 表达式 部分并没有引起太多关注。 Microsoft 发布了一个完整的命名空间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();
        }
    }
}

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

【讨论】:

    【解决方案7】:

    它避免了必须在一个特定的地方只使用一次的方法,而不必在远离它们使用的地方定义。良好的用途是作为通用算法(例如排序)的比较器,然后您可以在其中定义一个自定义排序函数来调用排序,而不是在更远的地方强迫您查看其他地方以查看您正在排序的内容。

    这并不是真正的创新。 LISP 的 lambda 函数已有大约 30 年或更长时间了。

    【讨论】:

      【解决方案8】:

      您还可以在编写泛型代码以作用于您的方法时发现使用 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));
      

      【讨论】:

        【解决方案9】:

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

        【讨论】:

          【解决方案10】:

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

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

          考虑 lambda 表达式 x =&gt; 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. How can a delegate & interface be used interchangeably?

          【讨论】:

            【解决方案11】:

            很多时候,您只是在一个地方使用该功能,因此创建一个方法只会使类变得混乱。

            【讨论】:

              【解决方案12】:

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

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

              【讨论】:

                【解决方案13】:

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

                例如,在您可能使用过 SQL 并可能受到 SQL 注入攻击之前,因为黑客在通常需要数字的地方传递了一个字符串。现在您将使用一个 LINQ lambda 表达式,它受到保护。

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

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

                【讨论】:

                  【解决方案14】:

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

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

                  【讨论】:

                    【解决方案15】:

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

                    此外,由于它们描述的是一次性使用代码,因此它们不必成为会导致更多代码复杂性的类的成员。想象一下,每次我们想要配置类对象的操作时,都必须声明一个焦点不明确的类。

                    【讨论】:

                      猜你喜欢
                      • 2012-04-17
                      • 2010-09-09
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2017-04-17
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多