Planując swoje programy, często zaczynam od takiego łańcucha myśli:
Drużyna piłkarska to tylko lista piłkarzy. Dlatego powinienem to przedstawić za pomocą:
var football_team = new List<FootballPlayer>();
Kolejność na tej liście odpowiada kolejności, w jakiej gracze są umieszczeni na liście.
Ale później zdaję sobie sprawę, że drużyny mają także inne właściwości, poza samą listą graczy, które muszą zostać zarejestrowane. Na przykład bieżąca suma wyników w tym sezonie, aktualny budżet, jednolite kolory, string
nazwa drużyny itp.
Więc myślę:
W porządku, drużyna piłkarska jest jak lista zawodników, ale dodatkowo ma nazwę (a
string
) i sumę wyników (anint
). .NET nie zapewnia klasy do przechowywania drużyn piłkarskich, więc stworzę własną klasę. Najbardziej podobna i odpowiednia istniejąca struktura jestList<FootballPlayer>
, więc odziedziczę po niej:class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal }
Ale okazuje się, że wytyczna mówi, że nie powinieneś dziedziczyćList<T>
. Jestem całkowicie zdezorientowany tymi wytycznymi pod dwoma względami.
Dlaczego nie?
Najwyraźniej List
jest jakoś zoptymalizowany pod kątem wydajności . Jak to? Jakie problemy z wydajnością spowoduję, jeśli przedłużę List
? Co dokładnie się złamie?
Innym powodem, który widziałem, jest ten, który List
zapewnia Microsoft i nie mam nad nim kontroli, więc nie mogę go później zmienić po ujawnieniu „publicznego interfejsu API” . Ale staram się to zrozumieć. Co to jest publiczny interfejs API i dlaczego powinienem się tym przejmować? Jeśli mój obecny projekt nie ma i prawdopodobnie nie będzie miał tego publicznego interfejsu API, czy mogę bezpiecznie zignorować tę wytyczną? Jeśli odziedziczę List
i okaże się, że potrzebuję publicznego interfejsu API, jakie będę miał trudności?
Dlaczego to w ogóle ma znaczenie? Lista jest listą. Co może się zmienić? Co mógłbym chcieć zmienić?
I na koniec, jeśli Microsoft nie chciał, żebym odziedziczył po nim List
, dlaczego nie stworzyli klasy sealed
?
Czego jeszcze mam użyć?
Najwyraźniej dla niestandardowych kolekcji Microsoft zapewnił Collection
klasę, którą należy rozszerzyć zamiast List
. Ale ta klasa jest bardzo naga i nie ma wielu przydatnych rzeczy, takich jakAddRange
na przykład. Odpowiedź jvitor83 zapewnia uzasadnienie wydajności dla tej konkretnej metody, ale w jaki sposób spowolnienie AddRange
nie jest lepsze niż brak AddRange
?
Dziedziczenie po Collection
to dużo więcej pracy niż dziedziczenie List
i nie widzę żadnej korzyści. Z pewnością Microsoft nie kazałby mi wykonywać dodatkowej pracy bez powodu, więc nie mogę się oprzeć wrażeniu, że coś w jakiś sposób nie rozumiem, a dziedziczenie Collection
nie jest właściwym rozwiązaniem dla mojego problemu.
Widziałem takie sugestie jak wdrożenie IList
. Po prostu nie. To jest dziesiątki wierszy kodu bojlera, który nic mi nie daje.
Na koniec niektórzy sugerują zawinięcie List
czegoś w:
class FootballTeam
{
public List<FootballPlayer> Players;
}
Są z tym dwa problemy:
To sprawia, że mój kod jest niepotrzebnie pełny. Muszę teraz zadzwonić
my_team.Players.Count
zamiast po prostumy_team.Count
. Na szczęście za pomocą C # mogę zdefiniować indeksatory, aby indeksowanie było przejrzyste i przekazywać wszystkie metody wewnętrzneList
... Ale to dużo kodu! Co dostanę za całą tę pracę?To po prostu nie ma sensu. Drużyna futbolowa nie ma „listy graczy”. To jest lista graczy. Nie mówisz: „John McFootballer dołączył do graczy SomeTeam”. Mówisz „John dołączył do SomeTeam”. Nie dodajesz litery do „znaków łańcucha”, dodajesz literę do łańcucha. Nie dodajesz książki do książek w bibliotece, dodajesz książkę do biblioteki.
Zdaję sobie sprawę, że to, co dzieje się „pod maską”, można powiedzieć, że „dodaje X do wewnętrznej listy Y”, ale wydaje się to bardzo sprzecznym z intuicją sposobem myślenia o świecie.
Moje pytanie (podsumowane)
Jaka jest prawidłowa C # sposób przedstawiający strukturę danych, która „logicznie” (to znaczy „dla ludzkiego umysłu”) jest tylko list
z things
kilkoma wodotryski?
Czy dziedziczenie po List<T>
zawsze jest nie do przyjęcia? Kiedy to jest dopuszczalne? Dlaczego? Dlaczego nie? Co musi wziąć pod uwagę programista, podejmując decyzję o dziedziczeniu List<T>
czy nie?
string
jest wymagane, aby zrobić wszystko, co object
może zrobić i więcej .