信不信由你,我继续把这个界面纳入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>
,以通过索引提供随机访问,而无需任何列表修改。
在对Jon Skeet原始答案的评论中(他质疑人们对IArray<T>
等合同的需求频率),我提到了Keys
和Values
Jon回复的SortedList<TKey, TValues>
类的属性分别为IList<TKey>
和IList<Value>
:
但在这种情况下,它被宣布为 IList,你知道只是使用 索引。 。 。 。这并不是很大 优雅,我同意 - 但事实并非如此 实际上让我感到痛苦。
这是合理的,但我会回答说它不会给你带来任何痛苦,因为你只是知道你不能这样做。但理由你知道不是,从代码中可以清楚地看出来;这是你有SortedList<TKey, TValue>
课程的经验。
如果我这样做,Visual Studio不会给我任何警告:
SortedList<string, int> mySortedList = new SortedList<string, int>();
// ...
IList<string> keys = mySortedList.Keys;
keys.Add("newkey");
根据IList<string>
,这是合法的。但我们都知道,它会导致异常。
Guillaume也提出了一个恰当的观点:
嗯,界面并不完美 但是dev可以检查IsReadOnly 打电话前的财产 添加/删除/设置...
同样,这是合理的,但是:这不会让你觉得有点迂回吗?
假设我按如下方式定义了一个接口:
public interface ICanWalkAndRun {
bool IsCapableOfRunning { get; }
void Walk();
void Run();
}
现在,假设我实现了这个接口的常见做法,但仅限于Walk
方法;在很多情况下,我会选择将IsCapableOfRunning
设置为false
并在NotSupportedException
上投放Run
...
然后我可能会有一些看起来像这样的代码:
var walkerRunners = new Dictionary<string, ICanWalkAndRun>();
// ...
ICanWalkAndRun walkerRunner = walkerRunners["somekey"];
if (walkerRunner.IsCapableOfRunning) {
walkerRunner.Run();
} else {
walkerRunner.Walk();
}
我是疯了,还是这种打败ICanWalkAndRun
界面的目的?
我发现在.NET中,当我设计一个具有通过索引提供随机访问的集合属性的类(或返回索引集合的方法等)时,非常奇怪,但不应该或者无法通过添加/删除项目来修改,如果我想以“正确的方式”进行操作,并提供一个界面以便我可以在不破坏API的情况下更改内部实现,我必须使用{ {1}}。
标准方法似乎是采用IList<T>
的一些实现来明确定义方法IList<T>
,Add
等 - 通常通过执行以下操作: / p>
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
方法。
当然,我总是可以编写这个接口并自己使用它,但这对所有未实现它的预先存在的.NET类没有帮助。 (是的,我知道我可以编写一个包含任何RemoveAt
并吐出IList<T>
的包装器,但是......认真吗?)
有没有人了解为什么IArray<T>
中的接口是这样设计的?我错过了什么吗?是否有一个引人注目的论点反对我对明确定义System.Collections.Generic
成员的方法所说的问题?
我并不想听起来自大,好像我比设计.NET类和接口的人更了解;它对我来说没有意义 。但是我已经准备好承认我可能没有考虑过很多。
答案 0 :(得分:5)
设计问题并非总是黑白分明。
一方面是针对每种情况的精确接口,这使得实际实现接口的整个过程变得非常痛苦。
另一个是少数(呃)多用途接口,实现者并不总是完全支持这些接口,但使许多事情变得更容易,例如传递相似但不会在“确切”中分配的相同接口的实例界面“设计。
所以BCL设计师选择了第二种方式。有时候我也希望界面不那么多用途,特别是对于集合和C#4接口协变量/逆变量特征(不能应用于大多数集合接口,因为IEnumerable&lt;&gt;因为它们包含两个co - 以及逆变部分)。
另外,遗憾的是诸如字符串和基元类型之类的基类不支持某些接口,例如ICharStream(对于字符串,可以用于正则表达式等,以允许使用除string
以外的其他来源模式匹配的实例)或数字基元的IArithmetic,以便通用数学是可能的。但我想所有框架都有一些弱点。
答案 1 :(得分:2)
嗯,接口并不完美,但开发人员可以在调用Add / Remove / Set之前检查IsReadOnly属性......
答案 2 :(得分:0)
最接近的是返回 IEnumerable&lt; T&gt; 然后客户端(a.k.a.calller)可以自己调用.ToArray()。