【问题标题】:Why is there no IArray(T) interface in .NET?为什么 .NET 中没有 IArray(T) 接口?
【发布时间】:2010-01-21 15:34:14
【问题描述】:

2011 年 1 月 6 日更新:

信不信由你,我继续将此接口合并到an open source library I've started, Tao.NET。我wrote a blog post 解释了这个库的IArray<T> 接口,它不仅解决了我最初在这个问题中提出的问题(一年前?!)而且还提供了一个协变索引接口,这是非常缺乏的(在我看来)在 BCL 中。


问题(简​​而言之):

我问为什么.NET 有IList<T>,它实现了ICollection<T>,因此提供了修改列表的方法(AddRemove 等),但不提供任何中间接口,例如如IArray<T> 提供按索引随机访问,无需任何列表修改。


编辑 2010 年 1 月 21 日下午 2:22 EST:

在对 Jon Skeet 的原始答案的评论中(他在其中质疑一个人多久需要像IArray<T> 这样的合同),我提到SortedList<TKey, TValues>KeysValues 属性class 分别是 IList<TKey>IList<Value>,Jon 回复道:

但在这种情况下,它被声明为 IList,你知道只使用 索引器。 . . .不是很大 优雅,我同意——但事实并非如此 实际上给我带来任何痛苦。

这是合理的,但我会回答说它不会给你带来任何痛苦,因为你只是知道你做不到。但是你知道的原因并不是从代码中看的很清楚;是你对SortedList<TKey, TValue>类有经验。

如果我这样做,Visual Studio 不会给我任何警告:

SortedList<string, int> mySortedList = new SortedList<string, int>();

// ...

IList<string> keys = mySortedList.Keys;
keys.Add("newkey");

根据IList&lt;string&gt;,这是合法的。但我们都知道,它会导致异常。

纪尧姆也提出了一个恰当的观点:

嗯,界面并不完美 但开发人员可以检查 IsReadOnly 调用前的属性 添加/删除/设置...

同样,这是有道理的,但是:这不是让你觉得有点迂回吗?

假设我定义了一个接口如下:

public interface ICanWalkAndRun {
    bool IsCapableOfRunning { get; }

    void Walk();
    void Run();
}

现在,还假设我将实现此接口作为一种常见做法,但仅限于其Walk 方法;在很多情况下,我会选择将IsCapableOfRunning 设置为false 并在Run 上抛出NotSupportedException...

那么我可能有一些看起来像这样的代码:

var walkerRunners = new Dictionary<string, ICanWalkAndRun>();

// ...

ICanWalkAndRun walkerRunner = walkerRunners["somekey"];

if (walkerRunner.IsCapableOfRunning) {
    walkerRunner.Run();
} else {
    walkerRunner.Walk();
}

我是不是疯了,或者这是否违背了名为ICanWalkAndRun 的界面的目的?


原帖

我发现在 .NET 中,当我设计一个具有通过索引提供随机访问(或返回索引集合的方法等)的集合属性的类时,但不应该或不能通过添加/删除项目来修改,如果我想“做正确的事情”OOP 并提供一个接口以便我可以在不破坏 API 的情况下更改内部实现,我必须使用 @ 987654347@.

标准方法似乎是使用IList&lt;T&gt; 的一些实现,它明确定义了AddInsert 等方法——通常是通过执行以下操作:

private List<T> _items;
public IList<T> Items {
    get { return _items.AsReadOnly(); }
}

但我有点讨厌这个。如果另一个开发人员正在使用我的类,并且我的类有一个 IList&lt;T&gt; 类型的属性,并且接口的整个想法是:“这些是一些可用的属性和方法”,我为什么要抛出NotSupportedException(或任何情况)当他/她尝试做某事时,根据界面,应该是完全合法的?

我觉得实现一个接口并明确定义它的一些成员就像开一家餐馆并把一些项目放在菜单上——也许是在一些不起眼的、容易错过的部分菜单,但菜单上——根本不可用。

似乎应该有类似IArray&lt;T&gt; 接口的东西,它通过索引提供非常基本的随机访问,但不添加/删除,如下所示:

public interface IArray<T> {
    int Length { get; }
    T this[int index] { get; }
}

然后IList&lt;T&gt; 可以实现ICollection&lt;T&gt;IArray&lt;T&gt; 并添加其IndexOfInsertRemoveAt 方法。

当然,我总是可以只编写这个接口并自己使用它,但这对所有未实现它的预先存在的 .NET 类没有帮助。 (是的,我知道我可以编写一个包装器,它接受任何 IList&lt;T&gt; 并吐出一个 IArray&lt;T&gt;,但是......真的吗?)

有没有人知道为什么System.Collections.Generic 中的接口是这样设计的?我错过了什么吗?对于我在明确定义IList&lt;T&gt; 成员的方法的问题,是否有一个令人信服的论点反对

我并不想显得自大,好像我比设计 .NET 类和接口的人更了解;它只是没有意义对我。但我已经准备好承认有很多我可能没有考虑到。

【问题讨论】:

  • 好问题。由于我在 C++ 中进行了大量编程工作,因此我可以立即看到这样做的好处(提示随机访问迭代器)。
  • 你能链接到原帖吗?
  • @yodaj007:这是原始帖子。我只是在开头添加了很多内容。
  • 我没有看到“Jon Skeet 的原始答案”。这就是我问的原因。
  • 这是一个值得注意的更新,在 .NET 4.5 中存在与您的 IArray&lt;T&gt; 几乎相同的东西 IReadOnlyList&lt;T&gt;(并且是只读的 CollectionDictionary 接口)。

标签: .net collections interface readonly ilist


【解决方案1】:

设计问题并不总是黑白分明的。

一方面是针对每种情况的精确接口,这使得实际实现接口的整个过程非常痛苦。

另一个是少数(呃)多用途接口,它们并不总是得到实现者的完全支持,但使许多事情变得更容易,例如传递相似但不会获得在“确切”中分配的相同接口的实例界面”设计。

所以 BCL 设计者选择了第二种方式。有时我也希望接口少一点多用途,特别是对于集合和 C#4 接口协变/逆变特性(不能应用于大多数集合接口,除了 IEnumerable 因为它们都包含 co-以及逆变部分)。

此外,很遗憾,诸如字符串和原始类型之类的基类不支持某些接口,例如 ICharStream(对于字符串,可用于正则表达式等,以允许使用除 string 实例之外的其他源模式匹配)或 IArithmetic 用于数字基元,因此可以进行通用数学。但我想所有框架都有一些弱点。

【讨论】:

  • 只是回顾过去的一些问题,以确保我接受了值得接受的答案。这绝对是深思熟虑的,并提供了一些备受赞赏的见解。
  • 如果 IList 被定义为继承 IIndexable,而后者又继承了 IReadableByIndex,而后者又继承了 IEnumerable,这会给实现者带来怎样的痛苦呢?只需键入“Inherits IList”,所有必要的例程都将被填写,并且每个例程只需定义一次。
  • 需要知道 100 个不同的接口以及它们之间的详细区别会令人困惑;更不用说接口继承不能像您希望的那样正交工作;例如在子接口中添加属性 setter 或从两个基本接口继承相同的方法。 Google 的 Go 在这里有更好的解决方案;但对于 .NET 来说,这就是桥下的水。
  • 拥有大量接口对语言系统来说不会是问题,因为如果接口 X 派生自包含函数 foo() 的 Y,则将自动考虑 X.foo 的实现作为 Y.foo 的一个实现。然而,像 IList 这样的接口可能在已知之前就已经设计好了。恕我直言,至少每个接口的协变、逆变和类型无关方面,应该以可能的组合进行拆分。最棘手的一点是 IList.Contains 方法,在某种意义上它应该与类型无关:...
  • 如果将 IReadableList 传递给一些需要 IReadableList 的代码并且该代码想知道它是否包含特定的 Zebra,则 IReadableList 不应拒绝该请求因为它不是长颈鹿;它应该简单地回答“否”。
【解决方案2】:

好吧,接口并不完美,但开发人员可以在调用 Add/Remove/Set 之前检查 IsReadOnly 属性...

【讨论】:

  • IsReadOnly 几乎被破坏了......因为它没有区分列表本身和它的元素(“不允许......修改元素”)。因此,大多数not 允许Add/Remove still 的类在IsReadOnly 的意义上不是只读的。是的,这很糟糕。
  • IList(非泛型)包含 IsFixedSize 属性。
  • 是的,但 IList&lt;T&gt; 不继承自 IList - 您需要同时实现两者。糟透了。
  • @KonradRudolph:如果 T 是一个引用类型,IList&lt;T&gt; 用于识别多个 T 实例(作为第一个、第二个、第三个等)由于IList&lt;T&gt; 用于识别,而不是包含T 的实例,这些实例的任何属性都不适用于@ 的任何部分987654333@的状态;对于可变类类型T 的“只读”列表是否将突变限制在其成员中,没有“歧义”。它不能,所以它没有。真正的问题是缺少一个属性来说明IList&lt;T&gt; 是否是不可变的(不可变意味着只读,反之则不然)。
  • @supercat 不确定我是否理解这一点。如果我没记错的话,我说的是有IList 实现禁止AddRemove,但IsReadOnly 返回false,因为它们提供了一个元素设置器。这是基于我现在找不到的其他一些讨论。 (或者可能是反过来;关键是实现可以根据this[]属性的设置器的存在来区分其元素是否可重新分配;这个IsReadOnly 属性没有充分覆盖。)
【解决方案3】:

最接近的方法是返回 IEnumerable,然后客户端(也称为调用者)可以自己调用 .ToArray()。

【讨论】:

    猜你喜欢
    • 2023-01-26
    • 2010-10-30
    • 2011-04-09
    • 2011-01-13
    • 2011-04-04
    • 2011-06-06
    • 2010-10-06
    • 1970-01-01
    • 2014-03-07
    相关资源
    最近更新 更多