Почему в .NET нет интерфейса IArray (T)? - PullRequest
15 голосов
/ 21 января 2010

Обновление 2011-Янв-06:

Верьте или нет, я пошел дальше и включил этот интерфейс в открытую библиотеку с открытым исходным кодом, Tao.NET . Я написал сообщение в блоге , объясняющее интерфейс IArray<T> этой библиотеки, который не только решает проблемы, которые я первоначально поднял в этом вопросе (год назад ?!), но также предоставляет ковариантный индексированный интерфейс что-то очень не хватает (по моему мнению) в BCL.


Вопрос (вкратце):

Я спросил, почему .NET имеет IList<T>, который реализует ICollection<T> и, следовательно, предоставляет методы для изменения списка (Add, Remove и т. Д.), Но не предлагает никакого промежуточного интерфейса, такого как IArray<T> для обеспечения произвольного доступа по индексу без изменения списка.


РЕДАКТИРОВАТЬ 2010-Jan-21 14:22 EST:

В комментарии к первоначальному ответу Джона Скита (в котором он спросил, как часто нужно иметь контракт, такой как 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");

Это законно, согласно IList<string>. Но мы все знаем, что это вызовет исключение.

Гийом также сделал правильное замечание:

Ну, интерфейсы не идеальны но разработчик может проверить IsReadOnly собственность перед звонком Добавить / Удалить / Set ...

Опять же, это разумно, НО : вам не кажется, что это немного странно?

Предположим, я определил интерфейс следующим образом:

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, я должен пойти с IList<T>.

Стандартный подход, по-видимому, заключается в использовании некоторой реализации 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; это просто не имеет смысла для меня . Но я готов признать, что есть много вещей, которые я, вероятно, не учел.

Ответы [ 3 ]

5 голосов
/ 21 января 2010

Вопросы проектирования не всегда черно-белые.

Одна сторона - это точные интерфейсы для каждой ситуации, что делает весь процесс фактической реализации интерфейсов реальной болью.

Другой - это несколько (er) многоцелевых интерфейсов, которые не всегда полностью поддерживаются разработчиком, но облегчают многие вещи, такие как передача экземпляров, которые похожи, но не получат те же интерфейсы, назначенные вдизайн «точного интерфейса».

Поэтому дизайнеры BCL решили пойти по второму пути.Иногда мне также хотелось бы, чтобы интерфейсы были немного менее многоцелевыми, особенно для коллекций и с функциями совместной / контравариантности интерфейса C # 4 (которые не могут применяться к большинству интерфейсов коллекций, за исключением IEnumerable <>, поскольку они содержат обеа также контравариантные части).

Кроме того, обидно, что базовые классы, такие как string и примитивные типы, не поддерживают некоторые интерфейсы, такие как ICharStream (для строк, которые могут использоваться для регулярных выражений и т.разрешить использование других источников, кроме string экземпляров для сопоставления с образцом) или IArithmetic для числовых примитивов, чтобы была возможна общая математика.Но я думаю, что у всех фреймворков есть свои слабые стороны.

2 голосов
/ 21 января 2010

Ну, интерфейсы не идеальны, но разработчик может проверить свойство IsReadOnly перед вызовом Add / Remove / Set ...

0 голосов
/ 21 января 2010

Самое близкое, что вы можете получить, это вернуть IEnumerable , и тогда клиент (a.k.a. caller) может вызвать .ToArray () самостоятельно.

...