背景:
Linq-To-Objects 具有扩展名method Count() http://msdn.microsoft.com/en-us/library/bb338038.aspx(不带谓词的重载)。当然,有时当一个方法只需要一个IEnumerable<out T>
(做 Linq),我们真的会传递一个“更丰富”的对象给它,比如ICollection<T>
。在这种情况下,实际迭代整个集合(即获取枚举器并“移动下一个”很多次)来确定计数是浪费的,因为有一个财产ICollection<T>.Count http://msdn.microsoft.com/en-us/library/5s3kzhec.aspx以此目的。而这个“快捷方式”从Linq开始就一直在BCL中使用。
现在,自 .NET 4.5(2012 年)以来,还有另一个非常好的界面,即IReadOnlyCollection<out T>
。它就像ICollection<T>
但它只包括那些成员return a T
。因此它可以是协变的T
("out T
“), 就像IEnumerable<out T>
,当项目类型可以或多或少派生时,这真的很好。但新的界面有它自己的属性,IReadOnlyCollection<out T>.Count http://msdn.microsoft.com/en-us/library/hh881496.aspx。参见其他地方那么为什么这些Count属性是不同的(而不仅仅是一个属性) https://stackoverflow.com/questions/12622539/.
问题:
Linq 的方法Enumerable.Count(this source)
确实检查ICollection<T>.Count
,但它不检查IReadOnlyCollection<out T>.Count
.
鉴于在只读集合上使用 Linq 确实很自然且很常见,更改 BCL 来检查两个接口是个好主意吗?我想这需要一项额外的类型检查。
这会是一个重大变化吗(假设他们没有“记得”从引入新界面的 4.5 版本开始执行此操作)?
示例代码
运行代码:
var x = new MyColl();
if (x.Count() == 1000000000)
{
}
var y = new MyOtherColl();
if (y.Count() == 1000000000)
{
}
where MyColl
是一种实现类型IReadOnlyCollection<>
但不是ICollection<>
,以及哪里MyOtherColl
是一种实现类型ICollection<>
。具体来说,我使用了简单/最小的类:
class MyColl : IReadOnlyCollection<Guid>
{
public int Count
{
get
{
Console.WriteLine("MyColl.Count called");
// Just for testing, implementation irrelevant:
return 0;
}
}
public IEnumerator<Guid> GetEnumerator()
{
Console.WriteLine("MyColl.GetEnumerator called");
// Just for testing, implementation irrelevant:
return ((IReadOnlyCollection<Guid>)(new Guid[] { })).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
Console.WriteLine("MyColl.System.Collections.IEnumerable.GetEnumerator called");
return GetEnumerator();
}
}
class MyOtherColl : ICollection<Guid>
{
public int Count
{
get
{
Console.WriteLine("MyOtherColl.Count called");
// Just for testing, implementation irrelevant:
return 0;
}
}
public bool IsReadOnly
{
get
{
return true;
}
}
public IEnumerator<Guid> GetEnumerator()
{
Console.WriteLine("MyOtherColl.GetEnumerator called");
// Just for testing, implementation irrelevant:
return ((IReadOnlyCollection<Guid>)(new Guid[] { })).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
Console.WriteLine("MyOtherColl.System.Collections.IEnumerable.GetEnumerator called");
return GetEnumerator();
}
public bool Contains(Guid item) { throw new NotImplementedException(); }
public void CopyTo(Guid[] array, int arrayIndex) { throw new NotImplementedException(); }
public bool Remove(Guid item) { throw new NotSupportedException(); }
public void Add(Guid item) { throw new NotSupportedException(); }
public void Clear() { throw new NotSupportedException(); }
}
并得到输出:
MyColl.GetEnumerator called
MyOtherColl.Count called
从代码运行来看,这表明在第一种情况下没有使用“快捷方式”(IReadOnlyCollection<out T>
)。 4.5 和 4.5.1 中看到相同的结果。
UPDATE用户在 Stack Overflow 上其他地方发表评论后supercat
.
当然,Linq 是在 .NET 3.5 (2008) 中引入的,IReadOnlyCollection<>
仅在 .NET 4.5 (2012) 中引入。然而,在这两者之间,还有一个特点,泛型中的协方差在 .NET 4.0 (2010) 中引入。正如我上面所说,IEnumerable<out T>
成为协变接口。但ICollection<T>
保持不变T
(因为它包含像void Add(T item);
).
早在 2010 年(.NET 4),这就产生了这样的后果:如果 Linq 的Count
扩展方法用于编译时类型的源IEnumerable<Animal>
例如,实际运行时类型是List<Cat>
,说,这肯定是一个IEnumerable<Cat>
而且,通过协方差,IEnumerable<Animal>
,那么“捷径”就是not用过的。这Count
扩展方法仅检查运行时类型是否为ICollection<Animal>
,但事实并非如此(无协方差)。它无法检查ICollection<Cat>
(它怎么知道什么是Cat
是它的TSource
参数等于Animal
?).
让我举个例子吧:
static void ProcessAnimals(IEnuemrable<Animal> animals)
{
int count = animals.Count(); // Linq extension Enumerable.Count<Animal>(animals)
// ...
}
then:
List<Animal> li1 = GetSome_HUGE_ListOfAnimals();
ProcessAnimals(li1); // fine, will use shortcut to ICollection<Animal>.Count property
List<Cat> li2 = GetSome_HUGE_ListOfCats();
ProcessAnimals(li2); // works, but inoptimal, will iterate through entire List<> to find count
我建议检查IReadOnlyCollection<out T>
也会“修复”这个问题,因为这是一个协变接口,它是由List<T>
.
结论:
- 还检查
IReadOnlyCollection<TSource>
在运行时类型的情况下将是有益的source
实施IReadOnlyCollection<>
但不是ICollection<>
因为底层集合类坚持是只读集合类型,因此希望not实施ICollection<>
.
- (新)还检查
IReadOnlyCollection<TSource>
即使当类型source
既是ICollection<>
and IReadOnlyCollection<>
,如果通用协方差适用。具体来说,IEnumerable<TSource>
可能真的是一个ICollection<SomeSpecializedSourceClass>
where SomeSpecializedSourceClass
可通过引用转换为TSource
. ICollection<>
不是协变的。然而,检查IReadOnlyCollection<TSource>
将通过协方差起作用;任何IReadOnlyCollection<SomeSpecializedSourceClass>
也是一个IReadOnlyCollection<TSource>
,并且将使用快捷方式。
- 成本是每次调用 Linq 时进行一次额外的运行时类型检查
Count
method.