c# - Linq 的 Enumerable.Count 方法检查 ICollection<> 但不检查 IReadOnlyCollection<>

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

背景:

Linq-To-Objects 的扩展名为 method Count() (不带谓词的重载)。当然,有时当一个方法只需要 IEnumerable<out T> 时(做 Linq),我们实际上会向它传递一个“更丰富”的对象,例如 ICollection<T> 。在这种情况下,实际迭代整个集合(即获取枚举器并“移动下一个”很多次)来确定计数是浪费的,因为有一个 property ICollection<T>.Count 以此目的。而这个“快捷方式”从 Linq 开始就一直在 BCL 中使用。

现在,自 .NET 4.5(2012 年)以来,还有另一个非常好的界面,即 IReadOnlyCollection<out T> 。就像 ICollection<T>只不过它只包含那些返回 T 的成员。 。因此,它可以在 T 中协变。 ("out T "),就像 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 来检查这两个接口(interface)是一个好主意吗?我想这将需要额外的一个类型检查。

这会是一个重大变化吗(假设他们没有“记得”从引入新界面的 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

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)
    // ...
}

然后:

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> 实现的协变接口(interface).

结论:

  1. 同时检查 IReadOnlyCollection<TSource>在运行时类型 source 的情况下会很有用实现IReadOnlyCollection<>但不是ICollection<>因为底层集合类坚持是只读集合类型,因此希望实现ICollection<> .
  2. (新)同时检查 IReadOnlyCollection<TSource>即使 source 的类型也是有益的都是 ICollection<>IReadOnlyCollection<> ,如果通用协方差适用。具体来说,IEnumerable<TSource>可能真的是ICollection<SomeSpecializedSourceClass>哪里SomeSpecializedSourceClass可通过引用转换为 TSourceICollection<>不是协变的。但是,检查 IReadOnlyCollection<TSource>将通过协方差起作用;任何IReadOnlyCollection<SomeSpecializedSourceClass>也是 IReadOnlyCollection<TSource> ,并且将使用快捷方式。
  3. 成本是每次调用 Linq 的 Count 进行一次额外的运行时类型检查。方法。

最佳答案

在许多情况下,一个类实现 IReadOnlyCollection<T>还将实现ICollection<T> 。因此,您仍然可以从 Count 属性快捷方式中受益。

参见ReadOnlyCollection例如。

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

由于检查其他接口(interface)以获取超出给定只读接口(interface)的访问权限的做法不好,因此这种方式应该没问题。

IReadOnlyInterface<T> 实现附加类型检查在Count()对于未实现 IReadOnlyInterface<T> 的对象的每次调用都将是额外的镇流器.

关于c# - Linq 的 Enumerable.Count 方法检查 ICollection<> 但不检查 IReadOnlyCollection<>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22940167/

相关文章:

c# - 如何选择使用默认命名空间的节点?

c# - 从 C 调用 C# DLL;连接问题

c# - 如何通过点击关闭TAB页面?

.net - SQL Server 数据库插入/更新 TCP 通知

.net - 线程关联和进程关联有什么区别?

c# - 几个函数的通用替换

sql - 透视不同表中的数据

c# - C# 中的 Task<T> 和 TaskContinuationOptions 说明?

c# - 结合正则表达式和 linq 在 C# 中分割字符串

LINQ to SQL - 无法翻译 null 的表达式检查