背景:
Linq-To-Objects的扩展名为method Count()
(不带谓词的重载)。当然,有时当一个方法只需要一个IEnumerable<out T>
(做Linq)时,我们真的会传递一个更丰富的&#34;对象,例如ICollection<T>
。在那种情况下,实际遍历整个集合(即获取调查员和&#34;接下来移动&#34;一大堆时间)以确定计数将是浪费的,因为有property ICollection<T>.Count
这个目的。这个&#34;捷径&#34;自Linq开始以来一直用于BCL。
现在,从.NET 4.5(2012年)开始,还有另一个非常好的界面,即IReadOnlyCollection<out T>
。它与ICollection<T>
类似,只是它包含那些返回 a T
的成员。出于这个原因,它可以在T
(&#34; out T
&#34;)中协变,就像IEnumerable<out T>
一样,当项目类型可以或多或少时,这非常好的。但新界面有自己的属性IReadOnlyCollection<out T>.Count
。见其他地方on SO why these Count
properties are distinct (instead of just one property)。
问题:
Linq的方法Enumerable.Count(this source)
会检查ICollection<T>.Count
,但不会检查IReadOnlyCollection<out T>.Count
。
鉴于在只读集合上使用Linq是非常自然和常见的,更改BCL以检查两个接口是否是个好主意?我想这需要一个额外的类型检查。
这会是一个重大改变(假设他们没有&#34;记得&#34;从引入新界面的4.5版本中做到这一点)?
示例代码
运行代码:
var x = new MyColl();
if (x.Count() == 1000000000)
{
}
var y = new MyOtherColl();
if (y.Count() == 1000000000)
{
}
其中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
从代码运行,显示&#34;快捷方式&#34;在第一种情况下没有使用(IReadOnlyCollection<out T>
)。在4.5和4.5.1中可以看到相同的结果。
supercat
在Stack Overflow上的其他地方发表评论后,更新 。
当然,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>
,然后是&#34;快捷方式&#34;是不使用。 Count
扩展方法仅检查运行时类型是否为ICollection<Animal>
,而不是(无协方差)。它无法检查ICollection<Cat>
(它如何知道Cat
是什么,TSource
参数等于Animal
?)。
让我举个例子:
static void ProcessAnimals(IEnuemrable<Animal> animals)
{
int count = animals.Count(); // Linq extension Enumerable.Count<Animal>(animals)
// ...
}
然后:
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>
支票将会修复&#34;这个问题也是如此,因为这是一个由List<T>
实现的协变接口。
结论:
IReadOnlyCollection<TSource>
的运行时类型实现source
而不是IReadOnlyCollection<>
,则检查ICollection<>
也是有益的,因为基础集合类坚持成为只读集合类型,因此希望不实施ICollection<>
。IReadOnlyCollection<TSource>
的类型同时为source
和ICollection<>
,检查IReadOnlyCollection<>
也是有益的。具体来说,IEnumerable<TSource>
可能真的是ICollection<SomeSpecializedSourceClass>
,SomeSpecializedSourceClass
可通过引用转换为TSource
转换为ICollection<>
。 IReadOnlyCollection<TSource>
不是协变的。但是,对IReadOnlyCollection<SomeSpecializedSourceClass>
的检查将通过协方差进行检查;任何IReadOnlyCollection<TSource>
也是Count
,并且将使用该快捷方式。答案 0 :(得分:3)
在许多情况下,实现IReadOnlyCollection<T>
的类也将实现ICollection<T>
。因此,您仍将从Count属性快捷方式中获益。
例如,请参阅ReadOnlyCollection。
public class ReadOnlyCollection<T> : IList<T>,
ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>,
IEnumerable<T>, IEnumerable
由于检查其他接口以获取超出给定只读接口的访问权限的不良做法,因此应该没问题。
对IReadOnlyInterface<T>
Count()
中的IReadOnlyInterface<T>
实施附加类型检查,对于未实现{{1}}的对象的每次调用都将是额外的镇流器。
答案 1 :(得分:2)
根据MSDN documentation,ICollection<T>
是获得此特殊待遇的唯一类型:
如果源的类型实现ICollection&lt; T&gt;,则该实现用于获取元素的数量。否则,此方法确定计数。
我猜他们没有看到为了这个优化而弄乱LINQ代码库(及其规范)是值得的。有很多CLR类型都有自己的Count
属性,但LINQ无法解释所有这些属性。