61

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

public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable
{
    int Count { get; }
}

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

public interface ICollection<T> : IEnumerable<T>, IEnumerable, *IReadOnlyCollection<T>*

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

ICollection<T>抽象可以看作是抽象的扩展IReadOnlyCollection<T>。请注意List<T>,例如,同时实现ICollection<T>IReadOnlyCollection<T>

然而,它并不是这样设计的。

我在这里想念什么?为什么会选择当前的实现呢?


更新

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

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

是比以下更好的设计:

  • ICollection<T>IReadOnlyCollection<T>直接执行

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

  1. 为什么不IList<T>执行IReadOnlyList<T>
  2. 为什么不IDictionary<T>执行IReadOnlyDictionary<T>
4

6 回答 6

42

可能有几个原因。这里有一些:

  • 巨大的向后兼容性问题

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

    interface ICollection<T> : IReadOnlyCollection<T>
    {
        int Count { get; }
    }
    

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

    好的,那怎么办:

    interface ICollection<T> : IReadOnlyCollection<T>
    {
        // Count is "inherited" from IReadOnlyCollection<T>
    }
    

    好吧,这破坏了所有决定Count显式实现的现有代码:

    class UnluckyClass : ICollection<Foo>
    {
         int ICollection<Foo>.Count { ... } // compiler error!
    }
    

    因此,在我看来,这个问题没有很好的解决方案:要么破坏现有代码,要么对每个人强制执行容易出错的实现。所以唯一的制胜之法就是不玩

于 2012-09-27T13:40:17.677 回答
17

Jon 就在这里https://stackoverflow.com/a/12622784/395144,您应该将他的回复标记为答案:

int ICollection<Foo>.Count { ... } // compiler error!

由于接口可以具有显式实现,因此提取基接口不向后兼容(使用基类就没有这个问题)。

这就是为什么...

Collection<T> : IReadOnlyCollection<T>
List<T> : IReadOnlyList<T>
Dictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>

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

恕我直言,他们最初犯了一个设计错误,现在完全无法解决(没有破坏)。

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

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) { }
}

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

于 2013-02-18T20:04:42.147 回答
0

这在语义上是错误的,因为显然,并非每个ICollection都是只读的。

也就是说,他们可以调用接口IReadableCollection,而可以调用实现ReadOnlyCollection

然而,他们并没有走那条路。为什么?我看到一个BCL 团队成员写道,他们不希望集合 API 变得过于复杂。(虽然它已经是,坦率地说。)

于 2012-09-27T13:43:35.790 回答
-3

当我第一次读到你的问题时,我想“嘿,他是对的!那是最有意义的。” 但是接口的重点是描述契约——行为的描述。如果你的类实现IReadOnlyCollection了,它应该是只读的。Collection<T>不是。因此,Collection<T>实现一个隐含只读的接口可能会导致一些非常讨厌的问题。an 的消费者IReadOnlyCollection将对底层集合做出某些假设——比如迭代它是安全的,因为没有人可以修改它,等等。如果它的结构像你建议的那样,那可能不是真的!

于 2012-09-27T13:32:31.443 回答
-3

面向对象的设计推理源于类与该类实现的任何接口之间的“Is A”关系。

在 .NET 4.5/c# 5.0List<T>中既是一个ICollection<T>又是IReadOnlyCollection<T>. 您可以将 a 实例List<T>化为任一类型,具体取决于您希望如何使用您的集合。

虽然拥有ICollection<T>implementIReadOnlyCollection<T>将使您能够List<T>成为ICollection<T>or IReadOnlyCollection<T>,但这样做会说ICollection<T> “是 A” IReadOnlyCollection<T>,而事实并非如此。

更新

ICollection<T>如果从实现的每个类继承,IReadOnlyCollection<T>那么我会说ICollection<T>实现接口更有意义。ICollection<T>既然情况并非如此,请考虑一下如果要实现它会是什么样子IReadOnlyCollection<T>

public interface IReadOnlyCollection<T> : IEnumerable<T>, IEnumerable{}
public interface ICollection<T> : IReadOnlyCollection<T> {}

如果您要查看 的签名ICollection<T>,它看起来像是IS-A只读集合。等等,让我们看看 IReadOnlyCollection 和啊……它实现了IEnumerable<T>IEnumerable所以它不一定是只读集合。从功能上讲,原始问题提出的实现是相同的,但从语义上讲,我认为将接口实现推到尽可能高的位置更正确。

于 2012-10-10T22:26:36.837 回答
-4

这个接口更像是一个描述接口,而不是真正的功能性东西。

在建议的方法中,每个集合都必须被理解为你只能阅读的东西,广告总是一样的。因此,您无法在创建后添加或删除事物。

我们可以问自己为什么调用该接口IReadOnlyCollection,而不是它使用的“IReadOnlyEnumerable” IEnumerable<T>, IEnumerable。答案很简单,这种类型的接口不会有任何意义,因为 Enumerable 已经是“只读的”。

所以让我们看看文档IReadOnlyCollection(T)

描述中的第一个(也是最后一个)senetece 说:

表示强类型的只读元素集合。

如果你知道强类型的用途,我认为这可以解释一切。

于 2012-09-27T13:59:22.163 回答