При планировании моих программ я часто начинаю с такой мысли:
Футбольная команда - это всего лишь список футболистов. Поэтому я должен представить его с помощью:
var football_team = new List<FootballPlayer>();Заказ этого списка представляет собой порядок, в котором игроки перечислены в списке.
Но я понимаю позже, что команды также имеют другие свойства, помимо простого списка игроков, которые должны быть записаны. Например, общее количество баллов в этом сезоне, текущий бюджет, равномерные цвета, string, отображающие имя команды и т.д.
Итак, я думаю:
Хорошо, футбольная команда точно так же, как и список игроков, но, кроме того, она имеет имя (a
string) и общее количество баллов (int)..NET не предоставляет класс для хранения футбольных команд, поэтому я сделаю свой собственный класс. Самая подобная и соответствующая существующая структураList<FootballPlayer>, поэтому я наследую ее:class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal }
Но оказывается, что руководство говорит, что вы не должны наследовать от List<T>. Я полностью смущен этим руководством в двух отношениях.
Почему бы и нет?
По-видимому List оптимизирован для производительности. Как так? Какие проблемы с производительностью я вызову, если я продолжу List? Что именно сломается?
Другая причина, по которой я видел, заключается в том, что List предоставляется Microsoft, и я не контролирую ее, поэтому я не могу ее изменить позже, после того, как вы откроете "открытый API" ". Но я не могу это понять. Что такое публичный API и почему меня это беспокоит? Если мой текущий проект не имеет и вряд ли когда-либо будет иметь этот общедоступный API, могу ли я смело игнорировать это руководство? Если я наследую от List, и, оказывается, мне нужен публичный API, какие трудности у меня есть?
Почему это даже имеет значение? Список - это список. Что может измениться? Что я могу изменить?
И, наконец, если Microsoft не хочет, чтобы я наследовал от List, почему они не сделали класс sealed?
Что еще я должен использовать?
По-видимому, для пользовательских коллекций Microsoft предоставила класс Collection, который следует расширить вместо List. Но этот класс очень голый и не имеет много полезных вещей, например, AddRange, например. jvitor83 answer обеспечивает обоснование производительности для этого конкретного метода, но как медленный AddRange не лучше, чем AddRange?
Унаследовано от Collection больше, чем наследование от List, и я не вижу никакой выгоды. Конечно, Microsoft не сказала бы мне, чтобы я делал дополнительную работу без причины, поэтому я не могу не чувствовать себя так, как будто я что-то неправильно понимаю, а наследование Collection на самом деле не является правильным решением для моей проблемы.
Я видел такие предложения, как реализация IList. Просто нет. Это десятки строк кода шаблона, который ничего мне не приносит.
Наконец, некоторые предлагают обернуть List во что-то:
class FootballTeam
{
public List<FootballPlayer> Players;
}
Есть две проблемы с этим:
-
Это делает мой код излишне подробным. Теперь я должен называть
my_team.Players.Countвместоmy_team.Count. К счастью, с С# я могу определить индексаторов, чтобы сделать индексирование прозрачным, и переслать все методы внутреннегоList... Но это много кода! Что я получу за все, что работает? -
Это просто не имеет никакого смысла. Футбольная команда не имеет "списка" игроков. Это список игроков. Вы не говорите: "Джон Макфуталлер присоединился к игрокам SomeTeam". Вы говорите: "Джон присоединился к SomeTeam". Вы не добавляете письмо к "строковым символам", вы добавляете письмо к строке. Вы не добавляете книгу в библиотечные книги, вы добавляете книгу в библиотеку.
Я понимаю, что то, что происходит "под капотом", можно сказать, "добавление X-Y внутреннего списка", но это похоже на очень противоречивый способ мышления о мире.
Мой вопрос (обобщенный)
Каков правильный способ С# для представления структуры данных, которая "логически" (то есть "для человеческого разума" ) является всего лишь List of things с несколькими колокольчиками и свистами?
Наследовать от List<T> всегда неприемлемо? Когда это приемлемо? Почему, почему нет? Что должен программист учитывать при принятии решения о наследовании от List<T> или нет?

