【问题标题】:Where to draw the line - is it possible to love LINQ too much? [closed]在哪里划清界限 - 是否有可能过于喜欢 LINQ? [关闭]
【发布时间】:2010-09-30 10:12:22
【问题描述】:

我最近发现了 LINQ,并且很喜欢它。我发现在很多情况下使用它比手写版本更具表现力,但一位同事发表了关于我滥用这项技术的评论,现在让我怀疑自己。我的观点是,如果一项技术工作高效且代码优雅,那么为什么不使用它呢?那是错的吗?我可以花额外的时间“手写”写出流程,虽然生成的代码可能会快几毫秒,但它的代码量要多 2-3 倍,因此出现错误的可能性要高 2-3 倍。

我的观点错了吗? 应该我应该用手写而不是使用 LINQ 来编写我的代码吗?这不就是 LINQ 的设计目的吗?

编辑:我说的是 LINQ to 对象,我不经常使用 LINQ to XML,我也使用过 LINQ to SQL,但我不像 LINQ to objects 那样迷恋这些风格。

【问题讨论】:

  • 您没有指定哪种风格....LINQ to Objects? LINQ转XML? LINQ到SQL?前两个我同意你,最后一个我不使用。

标签: c# .net vb.net linq


【解决方案1】:

我必须同意你的观点——如果写起来更高效、更优雅,那么几毫秒。编写额外的代码为错误提供了更多的空间,这是需要测试的额外代码,最重要的是需要维护的额外代码。想想那些将在您身后维护您的代码的人 - 他们会感谢您编写优雅易读的代码,然后再感谢您编写速度快几毫秒的代码!

但请注意,如果您考虑到大局,这几毫秒的成本可能会很大。如果那几毫秒是数千次重复循环的一部分,那么毫秒加起来很快。

【讨论】:

  • 我也是这么想的。我的观点是,只有当你竭尽全力寻找在错误的地方使用它的理由时,它才是滥用
【解决方案2】:

是的,你可能太喜欢 LINQ - Single Statement LINQ RayTracer

你在哪里画线?我想说尽可能多地使用 LINQ,因为它使代码更简单、更易于阅读。

当 LINQ 版本变得比非 LINQ 版本更难理解时,就该换掉了,反之亦然。编辑:这主要适用于 LINQ-To-Objects,因为其他 LINQ 风格各有优势。

【讨论】:

  • 天哪,LINQ 示例太荒谬了!!!我不得不说,我不太可能有时间破译这一切。我想这越过了理智的界限。
  • 划清界限?光线追踪器?我明白这个笑话。
  • 哈哈尼古拉斯,好一个!我同意,这是一个荒谬的例子,但我认为理解它的作用和作用也是一个非常有趣的挑战。
  • 加一个用于交换时间。尽管 linq 很酷(主要是指它的功能风格),但在性能、可读性等方面可能存在可伸缩性线。当 linq 接近这条线时,要小心,当它越过它时,交换。
【解决方案3】:

不可能太喜欢 Linq to Objects,这是一项非常棒的技术!

但是说真的,任何使您的代码易于阅读、易于维护并完成预期工作的任何东西,那么如果您不尽可能多地使用它,那就太愚蠢了。

【讨论】:

    【解决方案4】:

    LINQ 应该用于使来自各种来源的数据的过滤、排序、聚合和操作尽可能直观和富有表现力。我想说,只要你觉得它是最整洁、最有表现力和最自然的语法来做你想做的事,就用它,不要为此感到内疚。

    如果你开始搞砸文档,那么可能是时候重新考虑你的立场了。

    【讨论】:

    • 大声笑 - 是的,如果我在考虑编程时开始潮热,我会休假:P
    • 那么文档应该在最上面?
    • 我认为文档更像是程序员的女朋友或妻子——理论上它应该总是放在第一位,但实际上它大多会被遗忘,直到被严厉提醒它需要被记住!
    【解决方案5】:

    在这种情况下,记住优化的黄金法则很重要:

    1. 不要这样做
    2. 对于专家:不要这样做

    您绝对应该担心“滥用”linq,除非您可以明确指出它是导致性能问题的原因

    【讨论】:

    • 我不喜欢这个。有一种合理的观点类似于“不要过早地进行优化”。然而,这种态度似乎更像是“让它尽可能慢,你知道如何快速编码它”。如果要认真对待这一点,总是会选择幼稚的方法。
    【解决方案6】:

    就像任何事情一样,它可以被滥用。只要您远离明显的错误决定,例如

    var v = List.Where(...);
    for(int i = 0; i < v.Count(); i++)
    {...}
    

    并了解不同的执行方式是如何工作的,那么它很可能不会比普通方式慢很多。根据 Anders Hejlsburg(C# 架构师)的说法,C# 编译器在优化循环方面并不是特别擅长,但它在优化和并行化表达式树方面做得更好。随着时间的推移,它可能比循环更有效。 List 的 ForEach 版本实际上与 for 循环一样快,尽管我找不到证明这一点的链接。

    附:我个人最喜欢的是 ForEach 鲜为人知的表亲 IndexedForEach(利用扩展方法)

    List.IndexedForEach( (p,i) => 
     {
         if(i != 3)
            p.DoSomething(i);
     };
    

    【讨论】:

    • “差异执行”是什么意思?
    • 嗨,Bob,许多 LINQ 语句在您尝试使用它们之前实际上并没有做任何事情。例如,在调用 Count() 之前不会计算 List.Where()。但是,调用 count 将在每次循环迭代时评估语句,因此每次循环迭代都会评估 Linq 语句
    • 哦 - 推迟...是的,我现在关注。我以为我错过了一些称为“差异”执行的概念。
    • 要立即执行 var v 语句,可以调用 ToList 或 ToArray
    • 它在 O'Rielly LINQ 书籍和互联网上的名称不同。我也更喜欢延期
    【解决方案7】:

    LINQ 可以像艺术一样。继续使用它使代码更漂亮。

    【讨论】:

      【解决方案8】:

      您正在回答您自己的问题,即为几毫秒的性能编写 2-3 倍的代码。我的意思是,如果您的问题域需要加速,那么可以,如果不是,则可能不需要。但是,它真的只有几毫秒的性能还是 > 5% 或 > 10%。这是基于个案的价值判断。

      【讨论】:

      • 我想这是一个公平的观点 - 如果你要将循环的每次迭代增加几毫秒,那么它会在一定程度上降低性能 - 但是在大型处理循环的情况下,我可能会如果它意味着显着的差异,那么性能优于优雅的代码
      【解决方案9】:

      在哪里画线?

      好吧,我们已经知道实现自己的quicksort in linq 是个坏主意,至少与仅使用 linq 的 orderby 相比。

      【讨论】:

        【解决方案10】:

        我发现使用 LINQ 加快了我的开发速度,并且更容易避免循环可能引入的愚蠢错误。我曾经遇到过 LINQ 性能不佳的情况,但那是我使用它从具有数百万个节点的树结构中获取 excel 文件的数据之类的事情的时候。

        【讨论】:

          【解决方案11】:

          虽然我看到有人认为 LINQ 可能会使语句更难阅读,但我认为我的方法现在与他们正在解决的问题密切相关而不是花费时间这一事实远远超过了这一点包括查找循环或具有专用查找功能的杂乱类。

          花了一点时间才习惯使用 LINQ 做事,因为循环查找等一直是主要选项。我将 LINQ 视为另一种语法糖,它可以以更优雅的方式完成相同的任务。目前,我仍然在处理繁重的任务关键代码中避免使用它——但这只是在性能随着 LINQ 的发展而提高之前。

          【讨论】:

            【解决方案12】:

            我对 LINQ 唯一关心的是它的连接实现。

            正如我在尝试回答 this question 时确定的那样(并且已确认 here),LINQ 生成的执行连接的代码(我猜肯定是)很幼稚:对于列表中的每个项目,连接执行线性搜索加入的列表以查找匹配项。

            向 LINQ 查询添加连接实质上会将线性时间算法转换为二次时间算法。即使你认为过早的优化是万恶之源,从 O(n) 到 O(n^2) 的跳跃也应该让你停下来。 (如果你通过一个加入的项目加入另一个集合,也是 O(n^3)。)

            解决这个问题相对容易。例如,这个查询:

            var list = from pr in parentTable.AsEnumerable()
            join cr in childTable.AsEnumerable() on cr.Field<int>("ParentID") equals pr.Field<int>("ID")
            where pr.Field<string>("Value") == "foo"
            select cr;
            

            类似于在 SQL Server 中连接两个表的方式。但在 LINQ 中效率非常低:对于where 子句返回的每个父行,查询会扫描整个子表。 (即使你在一个未索引的字段上加入,SQL Server 也会构建一个哈希表来加速加入,如果可以的话。这有点超出 LINQ 的薪酬等级。)

            然而,这个查询:

            string fk = "FK_ChildTable_ParentTable";
            var list = from cr in childTable.AsEnumerable()
            where cr.GetParentRow(fk).Field<string>("Value") == "foo"
            select cr;
            

            产生相同的结果,但它只扫描子表一次。

            如果您使用 LINQ to 对象,同样的问题也适用:如果您想连接两个任意大小的集合,您可能需要考虑实施一种更有效的方法来查找连接的对象,例如:

            Dictionary<Foo, Bar> map = buildMap(foos, bars);
            var list = from Foo f in foos
            where map[f].baz == "bat"
            select f;
            

            【讨论】:

            • 这完全不真实。 LINQ 的连接运算符将内部序列中的所有元素加载到基于哈希表的高效查找中。您的特定示例效率低下,因为您在加入之后(而不是之前)进行过滤,但加入本身是完全有效的。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-01-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-07-11
            • 2010-09-15
            相关资源
            最近更新 更多