C# Count() 扩展方法性能
如果在具有 Count
属性(例如 List
),IEnumerable
上调用 LINQ Count()
扩展方法;Count()
方法是否查找该属性并返回它(而不是通过枚举来计算项目数)?以下测试代码似乎表明确实如此:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace CountSpeedTest
{
// Output:
// List - CLR : 0 ms
// Enumerate - CLR : 10 ms
// List - Mine: 12 ms
// Enumerate - Mine: 12 ms
class Program
{
private const int Runs = 10;
private const int Items = 1000000;
static void Main(string[] args)
{
var total = new long[] {0, 0, 0, 0};
for (int i = 0; i < Runs; ++i)
{
var items = Enumerable.Range(0, Items).Select(o => o.ToString()).ToList();
var list = new List<string>(items);
var enumerate = new Enumerate<string>(items);
total[0] += TimeCount(list, c => c.Count());
total[1] += TimeCount(enumerate, c => c.Count());
total[2] += TimeCount(list, c => c.SlowCount());
total[3] += TimeCount(enumerate, c => c.SlowCount());
}
Console.WriteLine(String.Format("List - CLR : {0} ms", total[0] / Runs));
Console.WriteLine(String.Format("Enumerate - CLR : {0} ms", total[1] / Runs));
Console.WriteLine(String.Format("List - Mine: {0} ms", total[2] / Runs));
Console.WriteLine(String.Format("Enumerate - Mine: {0} ms", total[3] / Runs));
Console.ReadKey(true);
}
private static long TimeCount<T>(IEnumerable<T> collection, Func<IEnumerable<T>, int> counter)
{
var stopwatch = Stopwatch.StartNew();
var count = counter(collection);
stopwatch.Stop();
if (count != Items) throw new Exception("Incorrect Count");
return stopwatch.ElapsedMilliseconds;
}
}
public static class CountExtensions
{
// Performs a simple enumeration based count.
public static int SlowCount<T>(this IEnumerable<T> items)
{
var i = 0;
var enumerator = items.GetEnumerator();
while (enumerator.MoveNext()) i++;
return i;
}
}
// Wraps an IEnumerable<T> to hide its Count property.
public class Enumerate<T> : IEnumerable<T>
{
private readonly IEnumerable<T> collection;
public Enumerate(IEnumerable<T> collection) { this.collection = collection; }
public IEnumerator<T> GetEnumerator() { return collection.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
}
相关说明:实现 IEnumerable
的自定义集合如何公开其自己的 Count 属性以使得 CLR
Count()
扩展方法可以利用它?
If the LINQ Count()
extension method is invoked on an IEnumerable<T>
that has a Count
property (e.g. List<T>
), does the Count()
method look for that property and return it (rather than counting the items by enumerating them)? The following test code seems to indicate that it does:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace CountSpeedTest
{
// Output:
// List - CLR : 0 ms
// Enumerate - CLR : 10 ms
// List - Mine: 12 ms
// Enumerate - Mine: 12 ms
class Program
{
private const int Runs = 10;
private const int Items = 1000000;
static void Main(string[] args)
{
var total = new long[] {0, 0, 0, 0};
for (int i = 0; i < Runs; ++i)
{
var items = Enumerable.Range(0, Items).Select(o => o.ToString()).ToList();
var list = new List<string>(items);
var enumerate = new Enumerate<string>(items);
total[0] += TimeCount(list, c => c.Count());
total[1] += TimeCount(enumerate, c => c.Count());
total[2] += TimeCount(list, c => c.SlowCount());
total[3] += TimeCount(enumerate, c => c.SlowCount());
}
Console.WriteLine(String.Format("List - CLR : {0} ms", total[0] / Runs));
Console.WriteLine(String.Format("Enumerate - CLR : {0} ms", total[1] / Runs));
Console.WriteLine(String.Format("List - Mine: {0} ms", total[2] / Runs));
Console.WriteLine(String.Format("Enumerate - Mine: {0} ms", total[3] / Runs));
Console.ReadKey(true);
}
private static long TimeCount<T>(IEnumerable<T> collection, Func<IEnumerable<T>, int> counter)
{
var stopwatch = Stopwatch.StartNew();
var count = counter(collection);
stopwatch.Stop();
if (count != Items) throw new Exception("Incorrect Count");
return stopwatch.ElapsedMilliseconds;
}
}
public static class CountExtensions
{
// Performs a simple enumeration based count.
public static int SlowCount<T>(this IEnumerable<T> items)
{
var i = 0;
var enumerator = items.GetEnumerator();
while (enumerator.MoveNext()) i++;
return i;
}
}
// Wraps an IEnumerable<T> to hide its Count property.
public class Enumerate<T> : IEnumerable<T>
{
private readonly IEnumerable<T> collection;
public Enumerate(IEnumerable<T> collection) { this.collection = collection; }
public IEnumerator<T> GetEnumerator() { return collection.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
}
On a related note: how can a custom collection which implements IEnumerable<T>
expose its own Count
property in such a way that the CLR Count()
extension method can take advantage of it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
它不会按名称查找
Count
属性,但会检查它是否实现ICollection
,然后使用该类型的Count
属性。来自文档:(显然这仅适用于不采用谓词的重载。)
因此,如果您想有效地获取计数,请确保实现
ICollection
。It doesn't look for a
Count
property by name, but it does check whether it implementsICollection<T>
, and then uses that type'sCount
property. From the documentation:(Obviously this only applies to the overload which doesn't take a predicate.)
So, if you want to obtain the count efficiently, make sure you implement
ICollection<T>
.是的,Enumerable.Count 方法确实会查找
ICollection
并在找到时使用它的 Count 属性。您可以通过查看反射器中的 Enumerable.Count 来验证这一点。仅当您使用不带附加参数的 Count 扩展方法时,这才是正确的。如果您使用带有谓词的版本,它将遍历可枚举元素。
Yes, the Enumerable.Count method will indeed look for
ICollection<T>
and use it's Count property if found. You can verify this by looking at Enumerable.Count in reflector.This is only true though if you use the Count extension method which takes no additional parameters. If you use the version which takes a predicate it will walk the enumerable elements.