何时不使用lambda表达式

时间:2009-03-23 10:49:11

标签: c# .net lambda anonymous-methods

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

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

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

  1. Anonymous Delegates vs Lambda Expressions vs Function Calls Performance
  2. Performance of foreach vs. List.ForEach
  3. .NET/C# Loop Performance Test (FOR, FOREACH, LINQ, & Lambda)
  4. DataTable.Select is faster than LINQ
  5. 选择合适的解决方案时,评估标准应该是什么?除了明显的原因,它使用lambda时更简洁的代码和可读性。

9 个答案:

答案 0 :(得分:32)

即使我将重点放在第一点,我首先要在整个性能问题上给出2美分。除非差异很大或者使用密集,否则我通常不会担心添加的微秒不会对用户产生任何明显的差异。我强调在考虑非密集调用方法时我只是不在乎。我在哪里有特殊的性能考虑因素是我设计应用程序本身的方式。我关心缓存,关于线程的使用,关于调用方法的聪明方法(是否进行多次调用或尝试只进行一次调用),是否汇集连接等等。事实上我通常都不喜欢不是关注原始性能,而是关注可扩展性。对于单个用户来说,我不关心它是否会以一小片纳秒的速度运行得更好,但我非常关心能够在没有注意到影响的情况下加载具有大量并发用户的系统。

话虽如此,这里是我对第1点的看法。我喜欢匿名方法。它们给了我极大的灵活性和代码优雅。匿名方法的另一个重要特性是它们允许我直接使用容器方法中的局部变量(从C#角度来看,当然不是从IL角度来看)。他们经常为我提供大量代码。我什么时候使用匿名方法? Evey单一时间我需要的代码片段在其他地方不需要。如果它在两个不同的地方使用,我不喜欢复制粘贴作为重用技术,所以我将使用普通的'委托'。所以,就像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表达式的IL更易读。再一次,没有性能影响,因为这些都是C#编译器技巧(不是JIT编译器技巧)。就像我不认为我们过度使用匿名方法一样,我不认为我们过度使用Lambda表达式来表示匿名方法。当然,相同的逻辑适用于重复的代码:不要做lambdas,使用常规委托。还有其他限制会导致您回到匿名方法或普通代理,例如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 :(得分:25)

我的回答不会受欢迎。

我认为Lambda的99%总是更好的选择,原因有三个。

首先,假设您的开发人员很聪明,绝对没有错。其他答案有一个潜在的前提,即每个开发人员,但你是愚蠢的。不是这样。

其次,Lamdas(等人)是一种现代语法 - 明天他们将比现在更常见。您的项目代码应该来自当前和新兴的约定。

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

总结一下:

  1. 开发人员可以处理它
  2. 每个人都在这样做
  3. 未来潜力
  4. 同样,我知道这不是一个受欢迎的答案。相信我“简单就是最好”也是我的口头禅。维护是任何来源的重要方面。我知道了。但我认为我们用一些陈词滥调的经验法则掩盖了现实。

    // Jerry

答案 2 :(得分:16)

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

答案 3 :(得分:14)

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

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

(我希望后者更快)

同样,如果你在讨论其他而不是内存中的对象,lambdas是维护类型检查(而不是一直解析字符串)方面最强大的工具之一。

当然,有些情况下,带代码的简单foreach将比LINQ版本更快,因为调用的次数会更少,并且调用会花费很少但可测量的时间。但是,在许多情况下,代码根本不是瓶颈,更简单的代码(特别是对于分组等)的价值远远超过几纳秒。

另请注意,在.NET 4.0中,有additional Expression nodes用于循环,逗号等内容。语言不支持它们,但运行时支持它们。我只是为了完整性而提到这一点:我当然不是说你应该使用Expression那样的foreach构造!

答案 4 :(得分:6)

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

何时选择什么?我想这取决于情况,但也取决于人。举个例子,有些人在普通的foreach循环中使用List.Foreach。我个人更喜欢后者,因为它通常更具可读性,但我可以与谁争论呢?

答案 5 :(得分:4)

经验法则:

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

答案 6 :(得分:4)

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

示例:

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

更好:

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

主要的例外是C#的类型推断由于某种原因而失败(并且有很多次是真的)。

答案 7 :(得分:2)

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

答案 8 :(得分:2)

Lambda表达很酷。 超过较早的delegate语法它们有一些优点,例如,它们可以转换为匿名函数或表达式树,参数类型可以从声明中推断出来,它们更清晰,更简洁等等当你需要一个匿名函数时,我认为不使用lambda表达式没有实际价值。早期风格的一个不那么大的优点是,如果不使用它们,你可以完全省略参数声明。像

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

当你必须声明一个什么都不做的空委托时,这很有用,但它不是的理由,不足以使用lambdas。

Lambdas让您编写快速匿名方法。现在,这使得lambdas在匿名方法变得毫无意义的地方毫无意义,即命名方法更有意义。 在命名方法上,匿名方法可能是不利的(本身并不是lambda表达式,但是从那时起lambdas广泛代表匿名方法它是相关的):

  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. 因为递归的lambdas需要多一行的怪癖,比如

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

  6. 除了第3点,其余的原因不使用 lambdas。

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