LINQ的Enumerable.Count方法检查的ICollection<>但不为IReadOnlyCollection&其中;>不为、方法、Enumerable、Count

2023-09-04 05:47:33 作者:说再多不如沉默。

背景:的

LINQ到对象的扩展方法计数() (过载不走predicate)。当然也有的时候,当一个方法只需要一个的IEnumerable<出T> (做LINQ),我们真的会传递一个更丰富的对象吧,如的ICollection< T> 。在这种情况下,它会浪费实际上是通过整个集合迭代(即获得枚举和移动下一个一大堆的时间)来确定数量,对于存在的属性的ICollection< T> .Count之间 用于这一目的。而这个捷径一直以来的LINQ的开始用在BCL。

现在,因为.NET(2012年)4.5,另外还有一个非常漂亮的界面,即 IReadOnlyCollection<出T> 。这就像的ICollection< T> ,但它仅包含那些构件的返回的一个 T 。出于这个原因,它可以在 T 出牛逼),就像被协变IEnumerable的<出T> ,这是非常好的,当项目类型可以或多或少地得到。但是,新的接口都有其自己的特性, IReadOnlyCollection<出T&GT ; .Count之间 。别处看到上那么,为什么这些计数性质是不同的(而不是仅仅一个属性)。

的问题:的

的LINQ的方法 Enumerable.Count(这个来源)不检查的ICollection< T> .Count之间,但它不检查 IReadOnlyCollection<出T>。.Count之间

由于它是真正天然的,共同使用LINQ只读的集合,会是一个好主意,改变BCL检查这两个接口?我想这将需要一个额外的类型检查。

和会是这样一个重大更改(因为他们没有记忆,从引进新的接口,它的4.5版本,这样做)?

样品code 的

运行code:

 变种X =新MYCOLL();
    如果(x.Count()==十亿)
    {
    }

    变种Y =新MyOtherColl();
    如果(y.Count()==十亿)
    {
    }
 
sql,,select termid ,count .....如何把第二次查询的总数与第一次的查询对应放在一起啊

其中, MYCOLL 是一种实施 IReadOnlyCollection<> 而不是的ICollection<&GT ; ,并在 MyOtherColl 是一种实施的ICollection<> 。具体来说,我用简单的/最小的类:

 类MYCOLL:IReadOnlyCollection<的Guid>
{
  公众诠释计数
  {
    得到
    {
      Console.WriteLine(MyColl.Count称为);
      //只是用于测试,执行不相关的:
      返回0;
    }
  }

  公众的IEnumerator<的Guid>的GetEnumerator()
  {
    Console.WriteLine(MyColl.GetEnumerator称为);
    //只是用于测试,执行不相关的:
    返回((IReadOnlyCollection<的Guid>)(新的GUID [] {}))的GetEnumerator()。
  }

  System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  {
    Console.WriteLine(MyColl.System.Collections.IEnumerable.GetEnumerator称为);
    返回的GetEnumerator();
  }
}
类MyOtherColl:ICollection的<的Guid>
{
  公众诠释计数
  {
    得到
    {
      Console.WriteLine(MyOtherColl.Count称为);
      //只是用于测试,执行不相关的:
      返回0;
    }
  }

  公共BOOL的IsReadOnly
  {
    得到
    {
      返回true;
    }
  }

  公众的IEnumerator<的Guid>的GetEnumerator()
  {
    Console.WriteLine(MyOtherColl.GetEnumerator称为);
    //只是用于测试,执行不相关的:
    返回((IReadOnlyCollection<的Guid>)(新的GUID [] {}))的GetEnumerator()。
  }

  System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  {
    Console.WriteLine(MyOtherColl.System.Collections.IEnumerable.GetEnumerator称为);
    返回的GetEnumerator();
  }

  公共BOOL包含(GUID项){抛出新的NotImplementedException(); }
  公共无效CopyTo从(GUID []数组,INT arrayIndex){抛出新的NotImplementedException(); }
  公共BOOL删除(GUID项){抛出新NotSupportedException异常(); }
  公共无效添加(GUID项){抛出新NotSupportedException异常(); }
  公共无效清除(){抛出新NotSupportedException异常(); }
}
 

和得到的输出:

 MyColl.GetEnumerator称为
MyOtherColl.Count名为

从code运行,这表明了快捷方式,在第一种情况下不使用( IReadOnlyCollection<出T> )。同样的结果见于4.5和4.5.1。

更新的按用户在其他地方对堆栈溢出后评论 supercat

的LINQ中引入了.NET 3.5(2008年),当然,和 IReadOnlyCollection<> 引入只能在.NET 4.5(2012年)。然而,在两者之间,另一个特点,协仿制药的介绍,在.NET 4.0(2010年)。正如我前面所说,的IEnumerable<出T> 成为协接口。但的ICollection< T> T (住不变,因为它包含的成员像无效添加(牛逼的项目);

早在2010(.NET 4)该有结果,如果LINQ的的计数扩展方法是用在编译时类型源 IEnumerable的<动物> 在实际运行时类型是例如名单,其中,猫> ,说,这肯定是一个的IEnumerable<猫> 而且,通过协方差,一个的IEnumerable<动物> ,那么快捷方式是的不是使用。该计数扩展方法检查,只有在运行时类型是的ICollection<动物> ,它不是(无协方差)。它不能检查的ICollection<猫> (会是怎样知道什么是是,它的 TSource 参数等于动物?)。

让我举一个例子:

 静态无效ProcessAnimals(IEnuemrable<动物>动物)
{
    诠释计数= animals.Count(); // LINQ的扩展Enumerable.Count<动物>(动物)
    // ...
}
 

然后:

 名单,其中,动物> LI1 = GetSome_HUGE_ListOfAnimals();
ProcessAnimals(LI1); //细,将使用快捷方式的ICollection<动物> .Count之间的财产

名单<猫> LI2 = GetSome_HUGE_ListOfCats();
ProcessAnimals(LI2); //作品,但inoptimal,会遍历整个列表<>找计数
 

我的建议检查 IReadOnlyCollection<出T> 将修复这个问题太大,因为这是由名单,其中实现的一个协接口; T>

结论:

同时检查 IReadOnlyCollection< TSource> 将是有益的情况下的源代码的运行时类型工具 IReadOnlyCollection<> 而不是的ICollection<> ,因为底层的集合类坚持做一个只读集合型因此希望的没有的实施的ICollection<> (新)还检查 IReadOnlyCollection< TSource> 是有益的,甚至当源的类型既是的ICollection<> IReadOnlyCollection<> ,如果通用的协方差适用。具体而言,的IEnumerable< TSource> 可能真的是一个的ICollection< SomeSpecializedSourceClass> ,其中 SomeSpecializedSourceClass 可转换通过引用转换为 TSource 的ICollection<> 不是协变的。然而,检查 IReadOnlyCollection< TSource> 将协工作;任何 IReadOnlyCollection< SomeSpecializedSourceClass> 也是 IReadOnlyCollection< TSource> ,在弹出的快捷将用于 的成本是每调用一个额外的运行时类型检查的LINQ的计数方法。 解决方案

在很多情况下,一个类实现 IReadOnlyCollection< T> 还将实施 ICollection的< T> 。所以,你仍然会从Count属性快捷方式获利。

请参阅 ReadOnlyCollection还例如

 公共类ReadOnlyCollection还< T> :IList的< T&GT ;,
    ICollection的< T&GT ;,的IList,ICollection的,IReadOnlyList< T&GT ;, IReadOnlyCollection< T&GT ;,
    IEnumerable的< T&GT ;,的IEnumerable
 

由于其糟糕的做法,以检查其他接口获得超越访问给定的只读接口应该是确定这种方式。

实施额外的类型检查 IReadOnlyInterface< T> 计数()将是额外的镇流器每次通话在对象上不实施 IReadOnlyInterface< T>

Background:

Linq-To-Objects has the extension method Count() (the overload not taking a predicate). Of course sometimes when a method requires only an IEnumerable<out T> (to do Linq), we will really pass a "richer" object to it, such as an ICollection<T>. In that situation it would be wasteful to actually iterate through the entire collection (i.e. get the enumerator and "move next" a whole bunch of times) to determine the count, for there is a property ICollection<T>.Count for this purpose. And this "shortcut" has been used in the BCL since the beginning of Linq.

Now, since .NET 4.5 (of 2012), there is another very nice interface, namely IReadOnlyCollection<out T>. It is like the ICollection<T> except that it only includes those member that return a T. For that reason it can be covariant in T ("out T"), just like IEnumerable<out T>, and that is really nice when item types can be more or less derived. But the new interface has its own property, IReadOnlyCollection<out T>.Count. See elsewhere on SO why these Count properties are distinct (instead of just one property).

The question:

Linq's method Enumerable.Count(this source) does check for ICollection<T>.Count, but it does not check for IReadOnlyCollection<out T>.Count.

Given that it is really natural and common to use Linq on read-only collections, would it be a good idea to change the BCL to check for both interfaces? I guess it would require one additional type check.

And would that be a breaking change (given that they did not "remember" to do this from the 4.5 version where the new interface was introduced)?

Sample code

Run the code:

    var x = new MyColl();
    if (x.Count() == 1000000000)
    {
    }

    var y = new MyOtherColl();
    if (y.Count() == 1000000000)
    {
    }

where MyColl is a type implementing IReadOnlyCollection<> but not ICollection<>, and where MyOtherColl is a type implementing ICollection<>. Specifically I used the simple/minimal classes:

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(); }
}

and got the output:

MyColl.GetEnumerator called
MyOtherColl.Count called

from the code run, which shows that the "shortcut" was not used in the first case (IReadOnlyCollection<out T>). Same result is seen in 4.5 and 4.5.1.

UPDATE after comment elsewhere on Stack Overflow by user supercat.

Linq was introduced in .NET 3.5 (2008), of course, and the IReadOnlyCollection<> was introduced only in .NET 4.5 (2012). However, in between, another feature, covariance in generics was introduced, in .NET 4.0 (2010). As I said above, IEnumerable<out T> became a covariant interface. But ICollection<T> stayed invariant in T (since it contains members like void Add(T item);).

Already in 2010 (.NET 4) this had the consequence that if Linq's Count extension method was used on a source of compile-time type IEnumerable<Animal> where the actual run-time type was for example List<Cat>, say, which is surely an IEnumerable<Cat> but also, by covariance, an IEnumerable<Animal>, then the "shortcut" was not used. The Count extension method checks only if the run-time type is an ICollection<Animal>, which it is not (no covariance). It can't check for ICollection<Cat> (how would it know what a Cat is, its TSource parameter equals Animal?).

Let me give an example:

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

My suggested check for IReadOnlyCollection<out T> would "repair" this issue too, since that is one covariant interface which is implemented by List<T>.

Conclusion:

Also checking for IReadOnlyCollection<TSource> would be beneficial in cases where the run-time type of source implements IReadOnlyCollection<> but not ICollection<> because the underlying collection class insists on being a read-only collection type and therefore wishes to not implement ICollection<>. (new) Also checking for IReadOnlyCollection<TSource> is beneficial even when the type of source is both ICollection<> and IReadOnlyCollection<>, if generic covariance applies. Specifically, the IEnumerable<TSource> may really be an ICollection<SomeSpecializedSourceClass> where SomeSpecializedSourceClass is convertible by reference conversion to TSource. ICollection<> is not covariant. However, the check for IReadOnlyCollection<TSource> will work by covariance; any IReadOnlyCollection<SomeSpecializedSourceClass> is also an IReadOnlyCollection<TSource>, and the shortcut will be utilized. The cost is one additional run-time type check per call to Linq's Count method.

解决方案

In many cases a class that implements IReadOnlyCollection<T> will also implement ICollection<T>. So you will still profit from the Count property shortcut.

See ReadOnlyCollection for example.

public class ReadOnlyCollection<T> : IList<T>, 
    ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, 
    IEnumerable<T>, IEnumerable

Since its bad practice to check for other interfaces to get access beyond the given readonly interface it should be ok this way.

Implementing an additional type check for IReadOnlyInterface<T> in Count() will be additional ballast for every call on an object which doesn't implement IReadOnlyInterface<T>.