При планировании моих программ я часто начинаю с такой мысли:
Футбольная команда - это всего лишь список футболистов. Поэтому я должен представить его с помощью:
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>
или нет?