【发布时间】:2016-04-27 06:00:25
【问题描述】:
我最近了解到,由 .NET 的 LINQ 实现创建的对象对于特定的枚举类型效率低下。
看看这段代码:
public class DummyCollection : ICollection<int>
{
public IEnumerator<int> GetEnumerator()
{
throw new Exception();
}
public int Count
{
get
{
return 10;
}
}
//some more interface methods
}
基本上,DummyCollection 实例的大小为 10,但如果实际枚举,则会引发异常。
现在在这里:
var d = new DummyCollection();
Console.WriteLine(d.Count());
打印出10没有错误,但是这段代码:
var l = d.Select(a=> a);
Console.WriteLine(l.Count());
抛出一个异常,尽管说 l 的大小也是 10 是微不足道的(因为 Select 提供 1 对 1 映射)。这基本上意味着,当检查 Ienumerable 的长度时,输入可能是 Select-wrapped Collection,从而将计算时间从 O(1) 延长到惊人的 O(n)(如果选择功能特别麻烦)。
我知道当您请求 LINQ 的泛型时会牺牲效率,但这似乎是一个很容易解决的问题。我在网上查了一下,找不到任何人解决这个问题。有没有办法绕过这个缺点?有人在调查这个吗?有人修这个吗?这只是一个没有太大问题的边缘案例吗?任何见解都值得赞赏。
【问题讨论】:
-
无论如何,在
GetEnumerator中抛出异常并能够在任何情况下调用Count()的真正用例是什么......? -
@MatíasFidemraizer 异常抛出只是一个占位符,以确保尽可能避免枚举。它可以被一个非常长的方法调用,或者只是一个非常大的集合,或者在枚举时发生变异的集合(例如锁定对象,这在异步环境中可能存在风险)来替换。
-
OrderBy有同样的问题。OrderBy不会更改计数,但调用Count()仍会执行完整排序。您无需使用一些反射技巧进行排序即可获得正确的计数。见stackoverflow.com/questions/17493076/… -
我认为通过调用 LINQ 方法,您期望它会枚举。这就像说
foreach应该尽可能避免枚举。您希望 LINQ 保护用户免于低效使用它。但是添加魔法会使推理(和控制)它的行为变得非常困难,尤其是。当方法被链接时。我会说“不要犯罪,如果你不能做到这一点。” -
@bentheiii 如果你实现
IEnumerable<T>,那么避免枚举又有什么意义呢?如果您不希望给定的对象是可枚举的,则无需实现整个接口。我错了吗? :O