计算IOrderedEnumerable而不使用它

我想做什么,短版:

var source = new[]{2,4,6,1,9}.OrderBy(x=>x); int count = source.Count; // <-- get the number of elements without performing the sort 

长版:

要确定IEnumerable中元素的数量,必须迭代所有元素。 这可能是一项非常昂贵的操作。

如果可以将IEnumerable转换ICollection ,则可以快速确定计数而无需迭代。 LINQ Count()方法自动执行此操作。

函数myEnumerable.OrderBy()返回一个IOrderedEnumerableIOrderedEnumerable显然不能转换为ICollection ,因此调用Count()将消耗整个事物。

但排序不会改变元素的数量,并且IOrderedEnumerable必须保持对其源的引用。 因此,如果该源是ICollection ,则应该可以从IOrderedEnumerable中确定计数而不消耗它。

我的目标是有一个库方法,它接受带有n个元素的IEnumerable ,然后例如检索位置为n / 2的元素;

我想避免迭代IEnumerable两次只是为了得到它的计数,但我也想避免创建一个不必要的副本,如果可能的话。


这是我想要创建的函数的框架

 public void DoSomething(IEnumerable source) { int count; // What we do with the source depends on its length if (source is ICollection) { count = source.Count(); // Great, we can use ICollection.Count } else if (source is IOrderedEnumerable) { // TODO: Find out whether this is based on an ICollection, // TODO: then determine the count of that ICollection } else { // Iterating over the source may be expensive, // to avoid iterating twice, make a copy of the source source = source.ToList(); count = source.Count(); } // do some stuff } 

让我们想一想这段代码实际上是什么样的:

 var source = new[]{ 2, 4, 6, 1, 9 }.OrderBy(x => x); int count = source.Count(); 

它是一样的

 int count = Enumerable.Count(Enumerable.OrderBy(new[]{ 2, 4, 6, 1, 9 }, x => x)); 

Enumerable.OrderBy(new[]{ 2, 4, 6, 1, 9 }, x => x)的结果传递给Count扩展。 你无法避免OrderBy执行。 因此它是非流操作符,它在返回内容之前消耗所有源,它将被传递给Count

因此,避免迭代所有集合的唯一方法是避免OrderBy – 在排序之前计算项目。


更新:您可以在任何OrderedEnumerable上调用此扩展方法 – 它将使用reflection来获取包含源序列的OrderedEnumerable source字段。 然后检查此序列是否为集合,并使用Count而不执行排序:

 public static class Extensions { public static int Count(this IOrderedEnumerable ordered) { // you can check if ordered is of type OrderedEnumerable Type type = ordered.GetType(); var flags = BindingFlags.NonPublic | BindingFlags.Instance; var field = type.GetField("source", flags); var source = field.GetValue(ordered); if (source is ICollection) return ((ICollection)source).Count; return ordered.Count(); } } 

用法:

 var source = new[]{ 2, 4, 6, 1, 9 }.OrderBy(x => x); int count = source.Count(); 

如果你想创建一个高性能的解决方案,我会考虑创建一个带有集合或IOrderedEnumerable等的重载。所有“是”和“as”类型检查和强制转换对你的那种东西都不好正在创造。

你正在重新发明轮子。 linq的“Count()”函数可以随心所欲地完成。

此外,添加this关键字并使其成为一个漂亮的扩展方法,以取悦自己和其他使用代码。

 DoSomething(this Collection source); DoSomething(this List source); DoSomething(this IOrderedEnumerable source); 

等等…

另一种方法是实现一个实现IOrderedEnumerable 。 然后,您可以实现将使常用Linq扩展方法短路的类成员,并提供查看原始枚举的计数方法。

 public class MyOrderedEnumerable : IOrderedEnumerable { private IEnumerable Original; private IOrderedEnumerable Sorted; public MyOrderedEnumerable(IEnumerable orig) { Original = orig; Sorted = null; } private void ApplyOrder(Func keySelector, IComparer comparer, bool descending) { var before = Sorted != null ? Sorted : Original; if (descending) Sorted = before.OrderByDescending(keySelector, comparer); else Sorted = before.OrderBy(keySelector, comparer); } #region Interface Implementations public IEnumerator GetEnumerator() { return Sorted != null ? Sorted.GetEnumerator() : Original.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IOrderedEnumerable CreateOrderedEnumerable( Func keySelector, IComparer comparer, bool descending) { var newSorted = new MyOrderedEnumerable(Original); newSorted.ApplyOrder(keySelector, comparer, descending); return newSorted; } #endregion Interface Implementations //Ensure that OrderBy returns the right type. //There are other variants of OrderBy extension methods you'll have to short-circuit public MyOrderedEnumerable OrderBy(Func keySelector) { Console.WriteLine("Ordering"); var newSorted = new MyOrderedEnumerable(Original); newSorted.Sorted = (Sorted != null ? Sorted : Original).OrderBy(keySelector); return newSorted; } public int Count() { Console.WriteLine("Fast counting.."); var collection = Original as ICollection; return collection == null ? Original.Count() : collection.Count; } public static void Test() { var nums = new MyOrderedEnumerable(Enumerable.Range(0,10).ToList()); var nums2 = nums.OrderBy(x => -x); var z = nums.Count() + nums2.Count(); } }