Hug*_*une 6 c# linq reflection performance ienumerable
我想做什么,短版:
var source = new[]{2,4,6,1,9}.OrderBy(x=>x);
int count = source.Count; // <-- get the number of elements without performing the sort
Run Code Online (Sandbox Code Playgroud)
长版:
要确定IEnumerable中元素的数量,必须迭代所有元素.这可能是一项非常昂贵的操作.
如果可以将IEnumerable转换为ICollection,则可以快速确定计数而无需迭代.LINQ Count()方法自动执行此操作.
函数myEnumerable.OrderBy()返回一个IOrderedEnumerable.一个IOrderedEnumerable显然不能被强制转换为ICollection的,因此调用COUNT()会消耗整个事情.
但排序不会改变元素的数量,IOrderedEnumerable必须保持对其源的引用.因此,如果该源是ICollection,则应该可以从IOrderedEnumerable中确定计数而不消耗它.
我的目标是有一个库方法,它接受带有n个元素的IEnumerable,然后例如检索位置为n/2的元素;
我想避免迭代IEnumerable两次只是为了得到它的计数,但我也想避免创建一个不必要的副本,如果可能的话.
这是我想要创建的函数的框架
public void DoSomething(IEnumerable<T> 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
}
Run Code Online (Sandbox Code Playgroud)
让我们来看看这段代码实际上是什么样的:
var source = new[]{ 2, 4, 6, 1, 9 }.OrderBy(x => x);
int count = source.Count();
Run Code Online (Sandbox Code Playgroud)
它是一样的
int count = Enumerable.Count(Enumerable.OrderBy(new[]{ 2, 4, 6, 1, 9 }, x => x));
Run Code Online (Sandbox Code Playgroud)
结果Enumerable.OrderBy(new[]{ 2, 4, 6, 1, 9 }, x => x)传递到Count扩展名.你无法避免OrderBy执行.因此它是非流媒体操作符,它会在返回内容之前消耗所有源,这将被传递给Count.
因此,避免迭代所有集合的唯一方法是避免OrderBy- 在排序之前计算项目.
更新:您可以在任何上调用此扩展方法OrderedEnumerable- 它将使用反射来获取保存源序列的source字段OrderedEnumerable<T>.然后检查此序列是否为集合,并在Count不执行排序的情况下使用:
public static class Extensions
{
public static int Count<T>(this IOrderedEnumerable<T> ordered)
{
// you can check if ordered is of type OrderedEnumerable<T>
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<T>)
return ((ICollection<T>)source).Count;
return ordered.Count();
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
var source = new[]{ 2, 4, 6, 1, 9 }.OrderBy(x => x);
int count = source.Count();
Run Code Online (Sandbox Code Playgroud)