Linq的Enumerable.Count方法检查ICollection<>但不适用于IReadOnlyCollection<>

时间:2014-04-08 14:31:46

标签: c# .net linq .net-4.5 base-class-library

背景:

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>实现的协变接口。

结论:

  1. 如果IReadOnlyCollection<TSource>的运行时类型实现source而不是IReadOnlyCollection<>,则检查ICollection<>也是有益的,因为基础集合类坚持成为只读集合类型,因此希望实施ICollection<>
  2. (新)如果适用通用协方差,即使IReadOnlyCollection<TSource>的类型同时为sourceICollection<>,检查IReadOnlyCollection<>也是有益的。具体来说,IEnumerable<TSource>可能真的是ICollection<SomeSpecializedSourceClass>SomeSpecializedSourceClass可通过引用转换为TSource转换为ICollection<>IReadOnlyCollection<TSource>不是协变的。但是,对IReadOnlyCollection<SomeSpecializedSourceClass>的检查将通过协方差进行检查;任何IReadOnlyCollection<TSource>也是Count,并且将使用该快捷方式。
  3. 每次调用Linq的{{1}}方法时,费用是一次额外的运行时类型检查。

2 个答案:

答案 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 documentationICollection<T>是获得此特殊待遇的唯一类型:

  

如果源的类型实现ICollection&lt; T&gt;,则该实现用于获取元素的数量。否则,此方法确定计数。

我猜他们没有看到为了这个优化而弄乱LINQ代码库(及其规范)是值得的。有很多CLR类型都有自己的Count属性,但LINQ无法解释所有这些属性。