为什么通用ICollection不在.NET 4.5中实现IReadOnlyCollection?

Zai*_*sud 52 c# oop design-patterns c#-5.0 .net-4.5

在.NET 4.5/C#5中,IReadOnlyCollection<T>使用Count属性声明:

public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable
{
    int Count { get; }
}
Run Code Online (Sandbox Code Playgroud)

我想知道,ICollection<T>实现IReadOnlyCollection<T>接口也没有意义:

public interface ICollection<T> : IEnumerable<T>, IEnumerable, *IReadOnlyCollection<T>*
Run Code Online (Sandbox Code Playgroud)

这意味着实现的类ICollection<T>将自动实现IReadOnlyCollection<T>.这对我来说听起来很合理.

ICollection<T>抽象可以被看作是所述的扩展IReadOnlyCollection<T>抽象.请注意List<T>,例如,实现两者ICollection<T>IReadOnlyCollection<T>.

然而,它并没有这样设计.

我在这里错过了什么?为什么要选择当前的实现呢?


UPDATE

我正在寻找一个使用面向对象设计推理来解释原因的答案:

  • 一个具体的类,如List<T>实现IReadOnlyCollection<T> ICollection<T>

是一个比以下更好的设计:

  • ICollection<T>IReadOnlyCollection<T>直接实施

另请注意,这基本上与以下问题相同:

  1. 为什么不IList<T>实施IReadOnlyList<T>
  2. 为什么不IDictionary<T>实施IReadOnlyDictionary<T>

Jon*_*Jon 36

可能有几个原因.这里有两个顶部:

  1. 巨大的向后兼容性问题

    你会如何写出定义ICollection<T>?这看起来很自然:

    interface ICollection<T> : IReadOnlyCollection<T>
    {
        int Count { get; }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    但它有一个问题,因为IReadOnlyCollection<T>还声明了一个Count属性(编译器会在这里发出警告).除了警告之外,保持原样(相当于写入new int Count)允许实现者通过显式实现至少一个来实现两个属性的不同实现Count.如果两个实现决定返回不同的值,这可能是"有趣的".允许人们用脚射击自己不是C#的风格.

    好的,那么呢:

    interface ICollection<T> : IReadOnlyCollection<T>
    {
        // Count is "inherited" from IReadOnlyCollection<T>
    }
    
    Run Code Online (Sandbox Code Playgroud)

    好吧,这会破坏所有决定Count明确实现的现有代码:

    class UnluckyClass : ICollection<Foo>
    {
         int ICollection<Foo>.Count { ... } // compiler error!
    }
    
    Run Code Online (Sandbox Code Playgroud)

    因此,在我看来,这个问题没有很好的解决方案:要么破坏现有代码,要么强制每个人都容易出错.所以唯一的胜利就是不打.

  2. 语义运行时类型检查

    编写像代码一样没有意义

    interface ICollection<T> : IReadOnlyCollection<T>
    {
        int Count { get; }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    (或等同于反思)除非ICollection<T>是"选择加入":如果IReadOnlyCollection<T>是一个超集,Count那么几乎所有太阳下的集合类都会通过这个测试,使其在实践中没用.

  • @Jon,第2点因多种原因无效.最重要的是,它错误地表达了IReadOnly*接口的目的 - 它们呈现集合的只读_view_,指示集合如何在特定上下文中使用.这并不表示该集合不会或不会发生变化.其次,有一个`ICollection.IsReadOnly`属性,它可以满足您的目的.运行时类型检查是作业的错误机制. (9认同)
  • @Jon,也许你会混淆只读和不可变?`IReadOnlyCollection <T>`的目的是让你编写一个方法来声明"我只需要读取这个集合,我保证我不会添加或删除任何项目"(忽略运行时强制转换的可能性,ew) .对于编写通用代码非常有用,这些代码不关心项目集合是否可变.作为另一个例子,由进程以只读方式打开的文件能够在进程的后面进行更改.然而,此过程不会以追加模式打开文件,因为它只想执行读取操作. (7认同)
  • 但是在当前的实现中,布尔条件`(new List <string>()是IReadOnlyCollection <Foo>)`计算为`true`. (6认同)
  • 不确定我理解你的第二个原因.我认为您提供的代码示例是可能的,有效的和可编译的."选择加入"是什么意思? (5认同)
  • @ZaidMasud:那里的写作需要一些改进.选择加入=你需要明确地说你是一个只读集合 - 与"每个人实现这一点相反,因为BCL团队这么说". (2认同)
  • 这是真正的答案:“int ICollection&lt;Foo&gt;.Count { ... } // 编译器错误!” (2认同)
  • C# 是否不考虑使用派生接口名称的显式接口声明,该接口继承成员与使用基名称的接口同义?VB.NET 毫无怨言地接受任一名称。 (2认同)
  • @jon,我想我也对你所说的“选择加入”的含义感到困惑。再次阅读时,我意识到您正在描述当前的行为(“List&lt;T&gt;”通过实现“IReadOnlyCollection&lt;T&gt;”和“ICollection&lt;T&gt;”而不仅仅是“ICollection&lt;T&gt;”来“选择加入”” )。您说“if (collection is IReadOnlyCollection&lt;Foo&gt;)”仅在“ICollection&lt;T&gt;”未实现“IReadOnlyCollection&lt;T&gt;”时才有用。我看不出这种行为何时“在实践中有用”——它只会使调用接受“IReadOnlyCollection&lt;T&gt;”的方法变得更加困难(例如,当您想将“IList&lt;T&gt;”传递给它时)。 (2认同)
  • 由于Darryl提到的原因,他们在第2点投票.你的答案传播了对IReadOnly*接口含义的过于常见的误解. (2认同)

Not*_*sxl 14

乔恩就在这里/sf/answers/883594911/,你应该将他的答复标记为答案:

int ICollection<Foo>.Count { ... } // compiler error!
Run Code Online (Sandbox Code Playgroud)

由于接口可以具有显式实现,因此提取基接口不向后兼容(对于基类,您没有此问题).

这就是为什么...

Collection<T> : IReadOnlyCollection<T>
List<T> : IReadOnlyList<T>
Dictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
Run Code Online (Sandbox Code Playgroud)

......但不是他们的界面.

恕我直言,他们最初做了一个设计错误,现在相当无法解决(没有破坏事情).

编辑:隐藏没有帮助,旧(显式)实现仍然不会构建(不修改代码):

interface INew<out T> { T Get(); }

interface IOld<T> : INew<T>
{
    void Set(T value);
    new T Get();
}

class Old<T> : IOld<T>
{
    T IOld<T>.Get() { return default(T); }
    void IOld<T>.Set(T value) { }
}
Run Code Online (Sandbox Code Playgroud)

'Sample.Old'没有实现接口成员'Sample.INew.Get()'