为什么.NET 中没有 IArray(T) 接口?

发布于 2024-08-19 02:26:08 字数 3914 浏览 2 评论 0 原文

2011 年 1 月 6 日更新:

不管你信不信,我继续将此接口合并到 一个开源库中已经开始了,淘网。我写了一篇博客文章解释了这个库的 IArray 接口,它不仅解决了我最初在这个问题中提出的问题(一年前?!)而且还提供了一个协变索引接口,这是 BCL 中非常缺乏的(在我看来)。


问题(简而言之):

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


美国东部时间 2010 年 1 月 21 日下午 2:22 编辑:

在对 Jon Skeet 原始答案的评论中(他质疑人们多久需要一次诸如 IArray 之类的合同),我提到 SortedList 类的 KeysValues 属性是 IListIList 分别,Jon 回复道:

但在本例中它被声明为 IList 并且您知道只需使用 索引器。 。 。 。这不是很大 优雅,我同意 - 但事实并非如此 实际上给我带来了任何痛苦。

这是合理的,但我会回答说它不会给你带来任何痛苦,因为你只是知道你做不到。但是您知道的原因并不是从代码中可以清楚地看出;而是您有使用 SortedList 类的经验。

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

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

// ...

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

根据 IList,这是合法的。但我们都知道,这会导致异常。

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

嗯,界面并不完美 但开发人员可以检查 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 的情况下更改内部实现,我必须使用 IList

标准方法似乎是使用 IList 的一些实现,它显式定义了 AddInsert 等方法。 ——通常是这样做:

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

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

我觉得实现一个接口并显式定义它的一些成员就像打开一家餐馆并将一些项目放在菜单上——也许在菜单的一些晦涩的、容易错过的部分中,但是尽管如此,在菜单上——这些根本不可用。

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

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

然后 IList< /code> 可以实现 ICollectionIArray 并添加其 IndexOfInsert 和 <代码>RemoveAt 方法。

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

有谁知道为什么 System.Collections.Generic 中的接口是这样设计的?我错过了什么吗?对于显式定义 IList 成员的方法,我所说的关于我的问题是否有令人信服的论据反对

我没有尝试听起来很自大,好像我比设计 .NET 类和接口的人更了解;这对我来说毫无意义。但我准备承认有很多事情我可能没有考虑到。

Update 2011-Jan-06:

Believe it or not, I went ahead and incorporated this interface into an open source library I've started, Tao.NET. I wrote a blog post explaining this library's IArray<T> interface, which not only addresses the issues I originally raised in this question (a year ago?!) but also provides a covariant indexed interface, something that's sorely lacking (in my opinion) in the BCL.


Question (in short):

I asked why .NET has IList<T>, which implements ICollection<T> and therefore provides methods to modify the list (Add, Remove, etc.), but doesn't offer any in-between interface such as IArray<T> to provide random access by index without any list modification.


EDIT 2010-Jan-21 2:22 PM EST:

In a comment to Jon Skeet's original answer (in which he questioned how often one would have any need for a contract such as IArray<T>), I mentioned that the Keys and Values properties of the SortedList<TKey, TValues> class are IList<TKey> and IList<Value>, respectively, to which Jon replied:

But in this case it's declared to be
IList and you know to just use the
indexers. . . . It's not hugely
elegant, I agree - but it doesn't
actually cause me any pain.

This is reasonable, but I would respond by saying that it doesn't cause you any pain because you just know you can't do it. But the reason you know isn't that it's clear from the code; it's that you have experience with the SortedList<TKey, TValue> class.

Visual Studio isn't going to give me any warnings if I do this:

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

// ...

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

It's legal, according to IList<string>. But we all know, it's going to cause an exception.

Guillaume made an apt point as well:

Well, the interfaces aren't perfect
but a dev can check the IsReadOnly
property before calling
Add/Remove/Set...

Again, this is reasonable, BUT: does this not strike you as a bit circuitous?

Suppose I defined an interface as follows:

public interface ICanWalkAndRun {
    bool IsCapableOfRunning { get; }

    void Walk();
    void Run();
}

Now, suppose as well that I made it a common practice to implement this interface, but only for its Walk method; in many cases, I would opt to set IsCapableOfRunning to false and throw a NotSupportedException on Run...

Then I might have some code that looked like this:

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

// ...

ICanWalkAndRun walkerRunner = walkerRunners["somekey"];

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

Am I crazy, or is this kind of defeating the purpose of an interface called ICanWalkAndRun?


Original Post

I find it very peculiar that in .NET, when I am designing a class with a collection property that provides random access by index (or a method that returns an indexed collection, etc.), but should not or cannot be modified by adding/removing items, and if I want to "do the right thing" OOP-wise and provide an interface so that I can change the internal implementation without breaking the API, I have to go with IList<T>.

The standard approach, it seems, is to go with some implementation of IList<T> that explicitly defines the methods Add, Insert, etc. -- typically by doing something like:

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

But I kind of hate this. If another developer is using my class, and my class has a property of type IList<T>, and the whole idea of an interface is: "these are some available properties and methods", why should I throw a NotSupportedException (or whatever the case may be) when he/she tries to do something that, according to the interface, should be completely legal?

I feel like implementing an interface and explicitly defining some of its members is like opening a restaurant and putting some items on the menu -- perhaps in some obscure, easy-to-miss part of the menu, but on the menu nonetheless -- that are simply never available.

It seems there ought to be something like an IArray<T> interface that provides very basic random access by index, but no adding/removing, like the following:

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

And then IList<T> could implement ICollection<T> and IArray<T> and add its IndexOf, Insert and RemoveAt methods.

Of course, I could always just write this interface and use it myself, but that doesn't help with all the pre-existing .NET classes that don't implement it. (And yes, I know I could write a wrapper that takes any IList<T> and spits out an IArray<T>, but ... seriously?)

Does anyone have any insight into why the interfaces in System.Collections.Generic were designed this way? Am I missing something? Is there a compelling argument against what I'm saying about my issues with the approach of explicitly defining members of IList<T>?

I'm not trying to sound cocky, as if I know better than the people who designed the .NET classes and interfaces; it just doesn't make sense to me. But I'm ready to acknowledge there's plenty I probably haven't taken into consideration.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

み青杉依旧 2024-08-26 02:26:08

设计问题并不总是非黑即白。

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

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

所以BCL设计师选择了第二条路。有时我也希望接口的多功能性少一点,特别是对于集合和具有 C#4 接口协变/逆变特性的接口(不能应用于大多数集合接口,除了 IEnumerable<>,因为它们同时包含协变和逆变特性) - 以及逆变部分)。

另外,令人遗憾的是,字符串和基元类型等​​基类不支持某些接口,例如 ICharStream(用于字符串,可用于正则表达式等,以允许使用除 string 之外的其他源)用于模式匹配的实例)或用于数字基元的 IArithmetic,以便通用数学成为可能。但我认为所有框架都有一些弱点。

Design questions are not always black and white.

One side is exact interfaces for each situation, which makes the whole process of actually implementing interfaces a real pain.

The other is few(er) multi-purpose interfaces which aren't always fully supported by the implementor but make many things easier, such as passing instances around which are similar but would not get the same interfaces assigned in the "exact interface" design.

So the BCL designers chose to go the second way. Sometimes I also wish that interfaces were a little less multi-purpose, especially for the collections and with the C#4 interface co-/contravariance features (which cannot be applied to most collection interfaces escept for IEnumerable<> because they contain both co- as well as contravariant parts).

Also, it's a shame that the base classes such as string and the primitive types do not support some interfaces such as ICharStream (for strings, which could be used for regex etc. to allow using other sources than string instances for pattern matching) or IArithmetic for numeric primitives, so that generic math would be possible. But I guess that all frameworks have some weak points.

把昨日还给我 2024-08-26 02:26:08

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

Well, the interfaces aren't perfect but a dev can check the IsReadOnly property before calling Add/Remove/Set...

机场等船 2024-08-26 02:26:08

最接近的结果是返回 IEnumerable< T > 然后客户端(也称为调用者)可以自己调用 .ToArray() 。

The closest you can get is to return IEnumerable< T > and then the client (a.k.a. caller) can call .ToArray() themselves.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文