何时不使用 lambda 表达式 [关闭]

Posted

技术标签:

【中文标题】何时不使用 lambda 表达式 [关闭]【英文标题】:When not to use lambda expressions [closed] 【发布时间】:2010-10-14 22:44:50 【问题描述】:

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

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

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

    Anonymous Delegates vs Lambda Expressions vs Function Calls Performance Performance of foreach vs. List.ForEach .NET/C# Loop Performance Test (FOR, FOREACH, LINQ, & Lambda)。 DataTable.Select is faster than LINQ

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

【问题讨论】:

我强烈建议您访问 www.lambdaexpression.net @Delashmate 域名现在被垃圾邮件发送者接管 【参考方案1】:

尽管我将专注于第一点,但我首先会在整个性能问题上给出我的 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 等)执行内容。是不是无论你是创建匿名方法还是发送表达式,调用语法都是一样的。

【讨论】:

+1 所有的好东西。一个小的更正:实例化调用和 GC-ing 匿名方法的开销以纳秒为单位,而不是微秒! 是的,当然,谢谢!!!大声笑【参考方案2】:

我的回答不会受欢迎。

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

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

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

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

总结一下:

    开发人员可以处理它 每个人都在这样做 有未来的潜力

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

// 杰瑞

【讨论】:

这个答案唯一的错误是第一句话的错误:) 我不敢相信我没有提到减少代码行数会增加源代码的可读性。我可以吸收 2,700 行远比 27,000 容易。 Lambda 有助于减少代码行数。 这个答案有两个问题,一个你没有回答这个问题,这个问题大约是那 1%。二,你直接从“更好”开始,但比什么更好?如果您的意思是与 delegate 关键字相比,我同意。如果您的意思比编写命名方法更好,那么我不同意 所以基本上,其他人都在做,谁在乎你的代码是否不可读。明白了。【参考方案3】:

代码重复。 如果您发现自己多次编写同一个匿名函数,则不应该是一个。

【讨论】:

这取决于复制代码的位置。假设您必须在一个方法中多次使用相同的条件对列表进行排序。我可能会在方法范围内声明一个 lambda 并重用它。但总的来说,我同意这一点! +1 嗯,不完全是。如果您的类型甚至很复杂,指定 lambda 的完整类型可能比复制几次要多得多。 这就是 'var' 的用途。 OTOH,如果您需要将 lambda 作为参数传递或返回,那么我同意。 我不知道这是否完全正确。复杂性呢?我认为多次写“p => p + 1”等没有任何问题;似乎提取它会更麻烦。 这是正确答案。接受的答案充满了陈词滥调和意见。【参考方案4】:

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

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

(我希望后者更快)

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

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

还要注意,在 .NET 4.0 中,additional Expression nodes 用于循环、逗号等。语言不支持它们,但运行时支持。我提到这一点只是为了完整:我当然不是说你应该使用手动 Expression 构造,而 foreach 会这样做!

【讨论】:

SLightly 题外话,但我的意思是听说 List.ForEach(. => .. ) 比使用 foreach( .. ) 更快/更有效/或某些东西 .. 因为它可以访问内部数组并且不使用枚举器等?虽然可能是错的...... 它可能有这样的访问权限,但它需要做委托调用,所以是双刃剑。您需要进行分析以确定,但我希望foreach 会稍微快一些。 具有讽刺意味的是,事实并非如此。信不信由你,List.ForEach 具有优势。单个委托的调用非常快(接近与接口调用相同的速度)。 @marc-gravell: "foreach" 将使用 List.Enumerator 和 List.ForEach 进行编译仍然更快。见:diditwith.net/2006/10/05/…msmvps.com/blogs/jon_skeet/archive/2006/01/20/foreachperf.aspx old-skool anon 的一个小优势。方法是,如果您对参数声明不感兴趣,可以省略它们,而 lambdas 需要声明正确数量的参数。 button.Click += 委托 Foo(); ; vs. button.Click += (sender, event) => Foo();【参考方案5】:

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

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

【讨论】:

我也没有争论:) 当结果是副作用时,我总是使用foreach。当然,所有 LINQ 乐趣的 Lambdas。【参考方案6】:

经验法则:

    编写自然且可读的代码。 避免代码重复(lambda 表达式可能需要一些额外的努力)。 仅在出现问题时进行优化,并且仅使用数据来备份问题的实际情况。

【讨论】:

“正确、清晰、简洁、快速。按此顺序”(Wes Dyer blogs.msdn.com/wesdyer/archive/2007/03/01/…) 确实如此。第一部分通常很少被谈论,“使其正确”。事实上,通常情况下,前三个都很好。如果你把它写对了,它的可读性就很好,这反过来又会很简洁。【参考方案7】:

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

例子:

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

更好:

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

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

【讨论】:

【参考方案8】:

如果需要递归,请不要使用 lambda,or you'll end up getting very distracted!

【讨论】:

与 F# 比较:"let rec y f x = f (y f) x" :) 我不同意。递归在任何方法中都不清楚。 @Jerry Nixon - 递归有时比替代方案更清晰。尝试通过mitpress.mit.edu/sicp @DanielEarwicker 我不认为递归 lambda 会分散注意力。这是一个极端的例子,即使写成命名方法也已经相当复杂了。 答案中的链接已过时。【参考方案9】:

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

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

当您必须声明一个不执行任何操作的空委托时,这很有用,但这并不是不使用 lambda 的充分理由。

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

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

    当不需要写一个时,比如:

    //this is unnecessary 
    Func<string, int> f = x => int.Parse(x);
    
    //this is enough
    Func<string, int> f = int.Parse;
    

    因为编写匿名迭代器块是不可能的。

    Func<IEnumerable<int>> f = () =>  yield return 0; ; //impossible
    

    因为递归 lambda 需要多一行古怪,比如

    Func<int, int> f = null;
    f = x => (x <= 1) ? 1 : x * f(x - 1);
    

    好吧,既然反射有点混乱,但这没有实际意义,不是吗?

除了第 3 点之外,其余的都不是强大 理由不使用 lambdas。

另请参阅 thread Func/Action 委托的缺点,因为它们通常与 lambda 表达式一起使用。

【讨论】:

以上是关于何时不使用 lambda 表达式 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

委托与匿名委托

如何在单个 lambda 表达式中同时使用更新和加入 [关闭]

在 c++ 中翻译 lambda 表达式 c# [关闭]

C#将字典的LINQ查询重构为单个lambda表达式[关闭]

C# Lambda 运算符 [关闭]

java 基本语法(十五)Lambda 函数式接口