【发布时间】: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>,因此提供了修改列表的方法(Add、Remove 等),但不提供任何中间接口,例如如IArray<T> 提供按索引随机访问,无需任何列表修改。
编辑 2010 年 1 月 21 日下午 2:22 EST:
在对 Jon Skeet 的原始答案的评论中(他在其中质疑一个人多久需要像IArray<T> 这样的合同),我提到SortedList<TKey, TValues> 的Keys 和Values 属性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<string>,这是合法的。但我们都知道,它会导致异常。
纪尧姆也提出了一个恰当的观点:
嗯,界面并不完美 但开发人员可以检查 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<T> 的一些实现,它明确定义了Add、Insert 等方法——通常是通过执行以下操作:
private List<T> _items;
public IList<T> Items {
get { return _items.AsReadOnly(); }
}
但我有点讨厌这个。如果另一个开发人员正在使用我的类,并且我的类有一个 IList<T> 类型的属性,并且接口的整个想法是:“这些是一些可用的属性和方法”,我为什么要抛出NotSupportedException(或任何情况)当他/她尝试做某事时,根据界面,应该是完全合法的?
我觉得实现一个接口并明确定义它的一些成员就像开一家餐馆并把一些项目放在菜单上——也许是在一些不起眼的、容易错过的部分菜单,但在菜单上——根本不可用。
似乎应该有类似IArray<T> 接口的东西,它通过索引提供非常基本的随机访问,但不添加/删除,如下所示:
public interface IArray<T> {
int Length { get; }
T this[int index] { get; }
}
然后IList<T> 可以实现ICollection<T> 和IArray<T> 并添加其IndexOf、Insert 和RemoveAt 方法。
当然,我总是可以只编写这个接口并自己使用它,但这对所有未实现它的预先存在的 .NET 类没有帮助。 (是的,我知道我可以编写一个包装器,它接受任何 IList<T> 并吐出一个 IArray<T>,但是......真的吗?)
有没有人知道为什么System.Collections.Generic 中的接口是这样设计的?我错过了什么吗?对于我在明确定义IList<T> 成员的方法的问题,是否有一个令人信服的论点反对?
我并不想显得自大,好像我比设计 .NET 类和接口的人更了解;它只是没有意义对我。但我已经准备好承认有很多我可能没有考虑到。
【问题讨论】:
-
好问题。由于我在 C++ 中进行了大量编程工作,因此我可以立即看到这样做的好处(提示随机访问迭代器)。
-
你能链接到原帖吗?
-
@yodaj007:这是原始帖子。我只是在开头添加了很多内容。
-
我没有看到“Jon Skeet 的原始答案”。这就是我问的原因。
-
这是一个值得注意的更新,在 .NET 4.5 中存在与您的
IArray<T>几乎相同的东西IReadOnlyList<T>(并且是只读的Collection和Dictionary接口)。
标签: .net collections interface readonly ilist