56

Stack Overflow 上正在回答很多问题,成员指定如何使用lambda 表达式解决这些现实世界/时间问题。

我们是否过度使用它,我们是否正在考虑使用 lambda 表达式对性能的影响?

我发现了几篇文章探讨了 lambda vs 匿名委托 vs for/foreach循环的性能影响,结果不同

  1. 匿名委托 vs Lambda 表达式 vs 函数调用性能
  2. foreach 与 List.ForEach 的性能
  3. .NET/C# 循环性能测试(FOR、FOREACH、LINQ 和 Lambda)
  4. DataTable.Select 比 LINQ 快

选择合适的解决方案时应该有什么评价标准?除了使用 lambda 时代码更简洁、更易读的明显原因。

4

9 回答 9

35

尽管我将专注于第一点,但我首先在整个性能问题上给出我的 2 美分。除非差异很大或使用量很大,否则我通常不会担心添加后对用户没有任何可见差异的微秒。我强调,我只在考虑非密集调用方法时才在意。我确实有特殊性能考虑的地方是我设计应用程序本身的方式。我关心缓存,关心线程的使用,关心调用方法的巧妙方法(是进行多次调用还是尝试只进行一次调用),是否池化连接等等。实际上我通常不关心不关注原始性能,而是关注可扩展性。我不在乎它是否对单个用户运行得更好一点纳秒,

话虽如此,我对第 1 点的看法如下。我喜欢匿名方法。它们给了我很大的灵活性和优雅的代码。匿名方法的另一个重要特性是它们允许我直接使用容器方法中的局部变量(当然,从 C# 的角度来看,而不是从 IL 的角度来看)。他们经常为我节省大量代码。我什么时候使用匿名方法?每次我需要的代码在其他地方都不需要。如果在两个不同的地方使用它,我不喜欢复制粘贴作为重用技术,所以我将使用普通的 ol' 委托。所以,就像 shoosh 回答的那样,重复代码是不好的。理论上没有性能差异,因为匿名是 C# 技巧,而不是 IL 东西。

我对匿名方法的大部分想法都适用于 lambda 表达式,因为后者可以用作表示匿名方法的紧凑语法。让我们假设以下方法:

public static void DoSomethingMethod(string[] names, Func<string, bool> myExpression)
{
    Console.WriteLine("Lambda used to represent an anonymous method");
    foreach (var item in names)
    {
        if (myExpression(item))
            Console.WriteLine("Found {0}", item);
    }
}

它接收一个字符串数组,对于每个字符串,它将调用传递给它的方法。如果该方法返回 true,它将显示“Found...”。您可以通过以下方式调用此方法:

string[] names = {"Alice", "Bob", "Charles"};
DoSomethingMethod(names, delegate(string p) { return p == "Alice"; });

但是,您也可以通过以下方式调用它:

DoSomethingMethod(names, p => p == "Alice");

两者之间的 IL 没有区别,因为使用 Lambda 表达式的那个更具可读性。再一次,没有性能影响,因为这些都是 C# 编译器技巧(不是 JIT 编译器技巧)。正如我不觉得我们过度使用匿名方法一样,我也不觉得我们过度使用 Lambda 表达式来表示匿名方法。当然,同样的逻辑也适用于重复代码:不要使用 lambda,使用常规委托。还有其他限制会导致您回到匿名方法或普通委托,例如 out 或 ref 参数传递。

Lambda 表达式的另一个好处是完全相同的语法不需要表示匿名方法。Lambda 表达式还可以表示……您猜对了,表达式。举个例子:

public static void DoSomethingExpression(string[] names, System.Linq.Expressions.Expression<Func<string, bool>> myExpression)
{
    Console.WriteLine("Lambda used to represent an expression");
    BinaryExpression bExpr = myExpression.Body as BinaryExpression;
    if (bExpr == null)
        return;
    Console.WriteLine("It is a binary expression");
    Console.WriteLine("The node type is {0}", bExpr.NodeType.ToString());
    Console.WriteLine("The left side is {0}", bExpr.Left.NodeType.ToString());
    Console.WriteLine("The right side is {0}", bExpr.Right.NodeType.ToString());
    if (bExpr.Right.NodeType == ExpressionType.Constant)
    {
        ConstantExpression right = (ConstantExpression)bExpr.Right;
        Console.WriteLine("The value of the right side is {0}", right.Value.ToString());
    }
 }

注意稍有不同的签名。第二个参数接收一个表达式而不是一个委托。调用此方法的方法是:

DoSomethingExpression(names, p => p == "Alice");

这与我们在使用 lambda 创建匿名方法时所做的调用完全相同。这里的区别在于我们不是创建匿名方法,而是创建表达式树。正是由于这些表达式树,我们可以将 lambda 表达式转换为 SQL,例如 Linq 2 SQL 所做的,而不是在引擎中为每个子句(如 Where、Select 等)执行内容。是无论您是创建匿名方法还是发送表达式,调用语法都是相同的。

于 2009-03-23T12:27:06.490 回答
27

我的回答不会受欢迎。

我相信 Lambda 的 99% 始终是更好的选择,原因有以下三个。

首先,假设您的开发人员很聪明,这绝对没有错。其他答案有一个基本前提,即除了您之外的每个开发人员都是愚蠢的。不是这样。

其次,Lamdas (et al) 是一种现代语法——明天它们将比现在更普遍。您的项目代码应该来自当前和新兴的约定。

第三,以“老式方式”编写代码对您来说似乎更容易,但对编译器来说却并不容易。这很重要,随着编译器的修订,遗留方法几乎没有机会改进。依赖编译器扩展它们的 Lambdas(等)可以受益,因为编译器会随着时间的推移更好地处理它们。

总结一下:

  1. 开发人员可以处理
  2. 每个人都在做
  3. 有未来的潜力

同样,我知道这不会是一个受欢迎的答案。相信我,“简单就是最好的”也是我的口头禅。维护是任何来源的重要方面。我得到它。但我认为我们用一些陈词滥调的经验法则掩盖了现实。

// 杰瑞

于 2011-12-20T22:27:36.427 回答
19

代码重复。
如果您发现自己不止一次编写相同的匿名函数,那么它不应该是一个。

于 2009-03-23T10:56:05.477 回答
14

好吧,当我们谈论委托使用时,lambda 和匿名方法之间不应该有任何区别——它们是相同的,只是语法不同。从运行时的角度来看,命名方法(用作委托)也​​是相同的。那么,区别在于使用委托与内联代码之间 - 即

list.ForEach(s=>s.Foo());
// vs.
foreach(var s in list) { s.Foo(); }

(我希望后者更快)

同样,如果您谈论的不是内存中的对象,那么就维护类型检查(而不是一直解析字符串)而言,lambda 是您最强大的工具之一。

当然,在某些情况下,简单foreach的 with 代码会比 LINQ 版本更快,因为要执行的调用会更少,并且调用花费的时间虽小但可衡量。然而,在许多情况下,代码根本不是瓶颈,更简单的代码(尤其是用于分组等)的价值远远超过几纳秒。

另请注意,在 .NET 4.0 中还有用于循环、逗号等内容的附加Expression节点。该语言不支持它们,但运行时支持。我提到这一点只是为了完整:我当然不是说你应该Expression在哪里使用手动构造foreach

于 2009-03-23T11:13:09.913 回答
6

我想说性能差异通常很小(显然,在循环的情况下,如果您查看第二篇文章的结果(顺便说一句,Jon Skeet 在这里有类似的文章)),您几乎不应该选择仅出于性能原因的解决方案,除非您正在编写一个性能绝对是首要的非功能性要求的软件,并且您确实必须进行微优化。

什么时候选择什么?我想这取决于情况,但也取决于人。举个例子,有些人更喜欢 List.Foreach 而不是普通的 foreach 循环。我个人更喜欢后者,因为它通常更具可读性,但我该反对谁呢?

于 2009-03-23T11:14:21.937 回答
4

任何时候 lambda 只是将其参数直接传递给另一个函数。不要为函数应用程序创建 lambda。

例子:

var coll = new ObservableCollection<int>();
myInts.ForEach(x => coll.Add(x))

更好:

var coll = new ObservableCollection<int>();
myInts.ForEach(coll.Add)

主要的例外是 C# 的类型推断由于某种原因而失败(而且很多时候都是如此)。

于 2009-03-23T17:57:04.670 回答
4

经验法则:

  1. 编写自然且可读的代码。
  2. 避免代码重复(lambda 表达式可能需要一点额外的努力)。
  3. 仅在出现问题时进行优化,并且仅使用数据来备份该问题的实际情况。
于 2009-03-23T11:28:48.643 回答
2

如果您需要递归,请不要使用 lambda,否则您最终会分心

于 2009-03-23T11:35:46.253 回答
2

Lambda 表达式很酷。与旧delegate语法相比,它们有一些优点,例如,它们可以转换为匿名函数或表达式树,从声明中推断出参数类型,它们更简洁,等等。我认为在以下情况下不使用 lambda 表达式没有真正的价值你需要一个匿名函数。早期样式的一个不太大的优势是,如果不使用参数声明,您可以完全省略它们。喜欢

Action<int> a = delegate { }; //takes one argument, but no argument specified

当您必须声明一个什么都不做的空委托时,这很有用,但这并不是不使用 lambda充分理由。

Lambda 让您可以编写快速的匿名方法。现在,在匿名方法变得毫无意义的地方,即命名方法更有意义的地方,这使得 lambdas 变得毫无意义。相对于命名方法,匿名方法可能是不利的(不是 lambda 表达式本身,但由于现在 lambda 广泛代表匿名方法,因此它是相关的):

  1. 因为它往往会导致逻辑重复(经常这样,重用很困难)

  2. 当没有必要写信时,例如:

    //this is unnecessary 
    Func<string, int> f = x => int.Parse(x);
    
    //this is enough
    Func<string, int> f = int.Parse;
    
  3. 因为编写匿名迭代器块是不可能的。

    Func<IEnumerable<int>> f = () => { yield return 0; }; //impossible
    
  4. 因为递归 lambda 需要多一行古怪,比如

    Func<int, int> f = null;
    f = x => (x <= 1) ? 1 : x * f(x - 1);
    
  5. 好吧,因为反射有点混乱,但这没有实际意义,不是吗?

除了第 3 点之外,其余的都不是不使用lambda充分理由。

另请参阅此线程,了解Func/Action委托的不利之处,因为它们通常与 lambda 表达式一起使用。

于 2013-12-20T12:51:18.337 回答