【问题标题】:Utility of List<T>.Sort() versus List<T>.OrderBy() for a member of a custom container class自定义容器类成员的 List<T>.Sort() 与 List<T>.OrderBy() 的实用程序
【发布时间】:2011-03-04 14:56:50
【问题描述】:

我发现自己在运行一些旧的 3.5 框架遗留代码,并发现有一些必须以同步方式更新的列表和字典。我已经确定,通过将这些合并到新自定义类的自定义容器类中,我可以使这个过程变得更容易使用和理解。然而,在某些地方,我开始关注通过特定的内部属性来组织这些新容器类的内容。比如按某一类的ID号属性排序。

由于容器类主要基于通用 List 对象,因此我的第一反应是使用 IComparable 编写内部类,并编写比较属性的 CompareTo 方法。这样,当我想调用排序时,我可以直接调用items.Sort()

但是,我一直在考虑改用items = items.OrderBy(Func)。如果我需要按任何其他属性排序,这种方式会更加灵活。可读性也更好,因为用于排序的属性将与排序调用一起列出,而不必查找 IComparable 代码。结果,整个实现感觉更干净。

我不关心过早或微优化,但我喜欢一致性。我发现最好在尽可能多的情况下坚持使用一种实现,并在必要时使用不同的实现。将我的代码转换为使用 LINQ OrderBy 而不是使用 List.Sort 是否值得?坚持这些自定义容器的 IComparable 实现是更好的做法吗?我应该权衡决定的任何一条路径是否提供了任何显着的机械优势?或者它们的最终功能是否等同于它只是成为编码器的偏好?

【问题讨论】:

    标签: c# linq sorting list


    【解决方案1】:

    这里的重点是List&lt;T&gt;.Sort() 进行就地排序。如果您的列表暴露给外部代码,它将始终代表该代码的同一个对象。如果列表通过容器类外部的代码保存在字段中,这一点很重要。如果使用OrderBy() 进行排序,每次都会得到一个新的枚举,替换之前的items。任何以前存储的列表都不会代表您班级的当前状态。

    考虑到性能,OrderBy 将不得不遍历整个列表来对项目进行排序。然后您将调用ToList() 以从此枚举创建新列表,并再次遍历该列表。另外,由于它是一个枚举,List 将使用加倍算法,增加它的大小直到每个元素都可以放入它。如果列表很大,那可能是相当多的分配和内存复制。我预计性能会比List&lt;T&gt;.Sort() 差很多。

    编辑:小基准:

    internal class Program {
    
        private static List<int> CreateList(int size) {
    
            // use the same seed so that every list has the same elements
            Random random = new Random(589134554);
    
            List<int> list = new List<int>(size);
            for (int i = 0; i < size; ++i)
                list.Add(random.Next());
            return list;
        }
    
        private static void Benchmark(int size, bool output = true) {
            List<int> list1 = CreateList(size);
            List<int> list2 = CreateList(size);
    
            Stopwatch stopwatch = Stopwatch.StartNew();
            list1.Sort();
            stopwatch.Stop();
            double elapsedSort = stopwatch.Elapsed.TotalMilliseconds;
            if (output)
                Console.WriteLine("List({0}).Sort(): {1}ms (100%)", size, elapsedSort);
    
            stopwatch.Restart();
            list2.OrderBy(i => i).ToList();
            stopwatch.Stop();
            double elapsedOrderBy = stopwatch.Elapsed.TotalMilliseconds;
            if (output)
                Console.WriteLine("List({0}).OrderBy(): {1}ms ({2:.00%})", size, elapsedOrderBy, elapsedOrderBy / elapsedSort);
    
        }
    
        internal static void Main() {
    
            // ensure linq library is loaded and initialized
            Benchmark(1000, false);
    
            Benchmark(10);
            Benchmark(100);
            Benchmark(1000);
            Benchmark(10000);
            Benchmark(100000);
            Benchmark(1000000);
    
            Console.ReadKey();
        }
    }
    

    输出(标准化为 List.Sort):

    List(10).Sort(): 0,0025ms (100%)
    List(10).OrderBy(): 0,0157ms (628,00%)
    List(100).Sort(): 0,0068ms (100%)
    List(100).OrderBy(): 0,0294ms (432,35%)
    List(1000).Sort(): 0,0758ms (100%)
    List(1000).OrderBy(): 0,3107ms (409,89%)
    List(10000).Sort(): 0,8969ms (100%)
    List(10000).OrderBy(): 4,0751ms (454,35%)
    List(100000).Sort(): 10,8541ms (100%)
    List(100000).OrderBy(): 50,3497ms (463,88%)
    List(1000000).Sort(): 124,1001ms (100%)
    List(1000000).OrderBy(): 705,0707ms (568,15%)
    

    【讨论】:

    • 好吧,您的基准测试表明的音量说明了...音量。以这样的速度,我什至可以挤入 3 个排序来代替我使用 3 个 OrderBy 的那个点,并且比我只使用一个 OrderBy 获得更好的性能!排序()它是!
    • 快速跟进,根据您的计算判断...这是否也意味着 items.First()items[0] 慢得多?
    • 不,First() 针对IList&lt;T&gt; 实现进行了优化,并将返回list[0]。我希望性能是一样的。即使是直接使用枚举,也只是一个item需要迭代,所以很可能是一个微优化。
    • 好吧,这是有道理的。此外,一些进一步的测试...调用 .Sort() 三次,甚至像 SchlaWiener 演示的那样指定单独的 IComparisons,比调用 OrderBy().ThenBy().ThenBy() 更快。
    • 此测试的结果会根据您对结果的处理方式发生巨大变化。例如,如果您只是将值输出到控制台窗口,则两种方法之间没有任何区别(10000 次迭代,差异为 0.93%)。总之,如果方法存在的原因是返回排序列表,那么 sort() 会更好,如果它是按特定顺序遍历列表,那么性能没有真正的差异。
    【解决方案2】:

    正如 Julien 所说,Sort() 可能会更快,但是由于您提到了 OrderBy() 更好的可读性和灵活性,您也可以使用 Sort() 方法来实现这一点(至少如果您想要的 Property基于你的排序是可比的)

    items = items.OrderBy(x => x.Name).ToList();
    items.Sort((x,y) => x.Name.CompareTo(y.Name)); // If x.Name is never null
    items.Sort((x,y) => String.Compare(x.Name, y.Name)); // null safe
    

    【讨论】:

    • 有一天,我将不再忘记诸如“我可以指定排序函数而不是依赖于 IComparable”之类的额外小事。这很好,因为这让我可以避免在某些类没有那种感觉时将它们指定为“可比较的”,它们只需要在某些情况下进行排序。我已将您的答案与 Julien 的数据结合起来,以取得成功。非常感谢!
    • 我更喜欢 IComparer 而不是 IComparable。这样你就可以传递排序算法的不同封装。
    猜你喜欢
    • 2011-07-13
    • 1970-01-01
    • 2012-04-20
    • 1970-01-01
    • 2010-11-07
    • 1970-01-01
    • 2015-11-05
    • 1970-01-01
    • 2010-10-10
    相关资源
    最近更新 更多