Dan*_*Tao 15 .net collections ilist interface readonly
信不信由你,我继续把这个界面整合到我开始的开源库中,Tao.NET.我写了一篇博客文章解释这个库的IArray<T>界面,它不仅解决了我最初在这个问题中提出的问题(一年前?!),而且还提供了协变索引界面,这在BCL中非常缺乏(在我看来).
我问为什么.NET有IList<T>,它实现ICollection<T>,因此提供的方法来修改列表(Add,Remove,等),但不提供任何在两者之间的接口,如IArray<T>提供通过索引没有任何列表修改随机访问.
在以乔恩斯基特原来的答案评论(其中他质疑多久人们就必须需要任何合同,如IArray<T>),我提到Keys和Values该属性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");
Run Code Online (Sandbox Code Playgroud)
据说这是合法的IList<string>.但我们都知道,它会导致异常.
纪尧姆也提出了一个恰当的观点:
好吧,接口并不完美,但开发人员可以在调用Add/Remove/Set之前检查IsReadOnly属性...
再次,这是合理的,但是:这不会让你觉得有点迂回吗?
假设我定义了一个接口如下:
public interface ICanWalkAndRun {
bool IsCapableOfRunning { get; }
void Walk();
void Run();
}
Run Code Online (Sandbox Code Playgroud)
现在,假设我实现了这个接口的通常做法,但仅限于它的Walk方法; 在许多情况下,我会选择设置IsCapableOfRunning到false与抛出一个NotSupportedException上Run...
然后我可能会有一些看起来像这样的代码:
var walkerRunners = new Dictionary<string, ICanWalkAndRun>();
// ...
ICanWalkAndRun walkerRunner = walkerRunners["somekey"];
if (walkerRunner.IsCapableOfRunning) {
walkerRunner.Run();
} else {
walkerRunner.Walk();
}
Run Code Online (Sandbox Code Playgroud)
我是疯了还是这种打败接口的目的ICanWalkAndRun?
我发现在.NET中,当我设计一个具有通过索引(或返回索引集合的方法等)提供随机访问的集合属性的类时非常奇怪,但是不应该或不能通过添加/来修改删除项目,如果我想"做正确的事"OOP-wise并提供一个接口,以便我可以更改内部实现而不破坏API,我必须去IList<T>.
标准的做法,似乎是一起去的一些实施IList<T>明确定义的方法Add,Insert等等-通常是通过做一些这样的:
private List<T> _items;
public IList<T> Items {
get { return _items.AsReadOnly(); }
}
Run Code Online (Sandbox Code Playgroud)
但我有点讨厌这个.如果另一个开发人员正在使用我的类,并且我的类具有类型的属性IList<T>,并且接口的整个概念是:"这些是一些可用的属性和方法",为什么我应该抛出NotSupportedException(或者不管情况如何)他/她试图做一些根据界面应该完全合法的事情?
我觉得自己实现一个接口,并明确界定它的一些成员是像打开一间餐厅,并把菜单上的某些项目-也许在一些不起眼,容易错过部分的菜单,但在菜单仍然-这根本就没有.
似乎应该有类似IArray<T>接口的东西,它通过索引提供非常基本的随机访问,但没有添加/删除,如下所示:
public interface IArray<T> {
int Length { get; }
T this[int index] { get; }
}
Run Code Online (Sandbox Code Playgroud)
然后,IList<T>可以实现ICollection<T>和IArray<T>并添加了IndexOf,Insert和RemoveAt方法.
当然,我总是可以编写这个接口并自己使用它,但这对所有未实现它的预先存在的.NET类没有帮助.(是的,我知道我可以写一个包装,拿出任何东西IList<T>并吐出来IArray<T>,但是......认真吗?)
有没有人知道为什么接口System.Collections.Generic是这样设计的?我错过了什么吗?是否有令人信服的理由对我说我的用显式定义的成员方法的问题是什么我IList<T>?
我不是试图听起来自大,好像我比设计.NET类和接口的人更了解; 它对我来说没有意义.但是我已经准备好承认我可能还没有考虑到很多.
设计问题并不总是黑白分明.
一方面是针对每种情况的精确接口,这使得实际实现接口的整个过程变得非常痛苦.
另一个是很少(呃)多用途接口,它们并不总是被实现者完全支持,但是使许多事情变得更容易,例如传递相似但不会在"精确接口"设计中分配相同接口的实例.
所以BCL设计师选择了第二种方式.有时候我也希望接口的多用途少一些,特别是对于集合和C#4接口协变量/逆变量特征(除了IEnumerable <>之外,它们不能应用于大多数集合接口,因为它们包含两个以及逆变部分).
同样,遗憾的是诸如字符串和基元类型之类的基类不支持某些接口,例如ICharStream(对于字符串,可以用于正则表达式等以允许使用除string模式匹配的实例之外的其他源)或IArithmetic数字基元,因此通用数学是可能的.但我想所有框架都有一些弱点.