Czy lepiej jest używać List<string>
adnotacji typu lub StringList
gdzie?StringList
class StringList : List<String> { /* no further code!*/ }
c#
inheritance
Ruudjah
źródło
źródło
StringList
iList<string>
? Nie ma żadnego, ale udało ci się (ogólne „ty”) dodać jeszcze jeden szczegół do podstawy kodu, który jest unikalny tylko dla twojego projektu i nic nie znaczy dla nikogo innego, dopóki nie przestudiują kodu. Po co maskować nazwę listy ciągów, aby nadal nazywać ją listą ciągów? Z drugiej strony, jeśli chcesz to zrobićBagOBagels : List<string>
, myślę, że ma to większy sens. Możesz iterować jako IEnumerable, zewnętrzni konsumenci nie dbają o wdrożenie - dobroć na okrągło.Odpowiedzi:
Jest jeszcze jeden powód, dla którego możesz chcieć dziedziczyć po typie ogólnym.
Microsoft zaleca unikanie zagnieżdżania typów ogólnych w podpisach metod .
Dlatego dobrym pomysłem jest utworzenie nazwanych nazw domen biznesowych dla niektórych ogólnych. Zamiast mieć
IEnumerable<IDictionary<string, MyClass>>
, utwórz typ,MyDictionary : IDictionary<string, MyClass>
a wtedy członek stanie sięIEnumerable<MyDictionary>
, co jest o wiele bardziej czytelne. Pozwala także korzystać z MyDictionary w wielu miejscach, zwiększając jawność kodu.Może to nie brzmieć jak ogromna korzyść, ale w prawdziwym kodzie biznesowym widziałem leki generyczne, które sprawią, że twoje włosy staną się koniec. Rzeczy lubią
List<Tuple<string, Dictionary<int, List<Dictionary<string, List<double>>>>>
. Znaczenie tego ogólnego nie jest w żaden sposób oczywiste. Jednak gdyby został skonstruowany z serii wyraźnie utworzonych typów, intencja oryginalnego programisty byłaby prawdopodobnie o wiele bardziej wyraźna. Co więcej, jeśli ten rodzaj ogólny musiałby się z jakiegoś powodu zmienić, znalezienie i zastąpienie wszystkich wystąpień tego typu ogólnego może być prawdziwym problemem, w porównaniu do zmiany go w klasie pochodnej.Wreszcie istnieje jeszcze jeden powód, dla którego ktoś może chcieć stworzyć własne typy na podstawie generycznych. Rozważ następujące klasy:
Skąd wiemy, który
ICollection<MyItems>
przekazać do konstruktora MyConsumerClass? Jeśli tworzymy klas pochodnychclass MyItems : ICollection<MyItems>
iclass MyItemCache : ICollection<MyItems>
, MyConsumerClass może być wtedy bardzo wyraźnie powiedzieć, który z dwóch zbiorów faktycznie chce. To działa bardzo dobrze z kontenerem IoC, który może bardzo łatwo rozwiązać dwie kolekcje. Pozwala także programistom na dokładność - jeśli ma sensMyConsumerClass
praca z dowolnym ICollection, mogą wziąć ogólny konstruktor. Jeśli jednak użycie jednego z dwóch typów pochodnych nigdy nie ma sensu, deweloper może ograniczyć parametr konstruktora do określonego typu.Podsumowując, często warto wyprowadzać typy z typów ogólnych - pozwala to na bardziej wyraźny kod tam, gdzie jest to stosowne i pomaga w czytelności i konserwacji.
źródło
Zasadniczo nie ma różnicy między dziedziczeniem po typach ogólnych a dziedziczeniem po czymkolwiek innym.
Dlaczego, u licha, miałbyś czerpać z jakiejkolwiek klasy i nie dodawać nic więcej?
źródło
Nie znam dobrze C #, ale uważam, że jest to jedyny sposób na implementację
typedef
, z którego programiści C ++ zwykle korzystają z kilku powodów:Zasadniczo porzucili ostatnią przewagę, wybierając nudne, ogólne nazwy, ale pozostałe dwa powody pozostają.
źródło
StringList
jestList<string>
- mogą one używane zamiennie. Jest to ważne podczas pracy z innymi interfejsami API (lub podczas udostępniania interfejsu API), ponieważ wszyscy inni na świecie go używająList<string>
.using StringList = List<string>;
jest najbliższym odpowiednikiem C # dla typedef. Takie podklasowanie zapobiegnie użyciu jakichkolwiek konstruktorów z argumentami.using
ograniczenie go do pliku kodu źródłowego, w którymusing
jest on umieszczony. Z tego punktu widzenia dziedziczenie jest bliższetypedef
. A utworzenie podklasy nie uniemożliwia dodania wszystkich potrzebnych konstruktorów (choć może to być uciążliwe).using
nie można użyć do tego celu, ale dziedziczenie może. To pokazuje, dlaczego nie zgadzam się z pozytywnym komentarzem Pete'a Kirkhama - nie uważam gousing
za prawdziwą alternatywę dla C ++typedef
.Mogę wymyślić co najmniej jeden praktyczny powód.
Deklarowanie typów nieogólnych, które dziedziczą z typów ogólnych (nawet bez dodawania czegokolwiek), pozwala na odwoływanie się do tych typów z miejsc, w których inaczej byłyby niedostępne lub dostępne w bardziej skomplikowany sposób niż powinny.
Jednym z takich przypadków jest dodanie ustawień za pomocą edytora ustawień w programie Visual Studio, gdzie wydaje się, że dostępne są tylko typy inne niż ogólne. Jeśli tam pojedziesz, zauważysz, że wszystkie
System.Collections
klasy są dostępne, ale żadne zbiory z nieSystem.Collections.Generic
wydają się odpowiednie.Innym przypadkiem jest tworzenie instancji przez XAML w WPF. Nie ma obsługi przekazywania argumentów typu w składni XML podczas korzystania ze schematu sprzed 2009 roku. Sytuację pogarsza fakt, że schemat z 2006 r. Wydaje się być domyślny dla nowych projektów WPF.
źródło
Myślę, że musisz zajrzeć do historii .
W przeciwnym razie Theodoros Chatzigiannakis miał najlepsze odpowiedzi, np. Wciąż istnieje wiele narzędzi, które nie rozumieją rodzajów ogólnych. A może nawet połączenie jego odpowiedzi i historii.
źródło
W niektórych przypadkach nie można używać typów ogólnych, na przykład nie można odwoływać się do typu ogólnego w XAML. W takich przypadkach możesz utworzyć rodzaj ogólny, który dziedziczy z typu ogólnego, którego pierwotnie chciałeś użyć.
Jeśli to możliwe, unikałbym tego.
W przeciwieństwie do typedef, tworzysz nowy typ. Jeśli użyjesz StringList jako parametru wejściowego do metody, twoi rozmówcy nie będą mogli przekazać
List<string>
.Ponadto musisz jawnie skopiować wszystkie konstruktory, których chcesz użyć, w przykładzie StringList nie możesz powiedzieć
new StringList(new[]{"a", "b", "c"})
, nawet jeśliList<T>
definiuje takiego konstruktora.Edytować:
Akceptowana odpowiedź skupia się na profesjonalistach, gdy używasz typów pochodnych w modelu domeny. Zgadzam się, że mogą być potencjalnie przydatne w tym przypadku, ale osobiście unikałbym ich nawet tam.
Ale nie należy (moim zdaniem) nigdy używać ich w publicznym interfejsie API, ponieważ albo utrudniasz życie dzwoniącemu, albo marnujesz wydajność (ja też tak naprawdę nie lubię niejawnych konwersji).
źródło
Dla tego, co jest warte, oto przykład, w jaki sposób dziedziczenie po klasie ogólnej jest używane w kompilatorze Roslyn Microsoftu, nawet bez zmiany nazwy klasy. (Byłem tak oszołomiony, że znalazłem się tutaj, szukając, czy to naprawdę możliwe).
W projekcie CodeAnalysis można znaleźć tę definicję:
Następnie w projekcie CSharpCodeanalysis istnieje następująca definicja:
Ta nietypowa klasa PEModuleBuilder jest szeroko stosowana w projekcie CSharpCodeanalysis, a kilka klas w tym projekcie dziedziczy po niej, bezpośrednio lub pośrednio.
A następnie w projekcie BasicCodeanalysis jest następująca definicja:
Ponieważ możemy (mam nadzieję) założyć, że Roslyn został napisany przez ludzi z rozległą wiedzą na temat języka C # i tego, jak należy go używać, myślę, że jest to zalecenie tej techniki.
źródło
Istnieją trzy możliwe powody, dla których ktoś próbowałby to zrobić z mojej głowy:
1) Aby uprościć deklaracje:
Jasne
StringList
iListString
mają taką samą długość, ale wyobraź sobie, że pracujesz zUserCollection
tak naprawdęDictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>>
lub z innym dużym, ogólnym przykładem.2) Aby pomóc AOT:
Czasami musisz używać kompilacji Ahead Of Time, a nie tylko kompilacji In Time - np. Programując dla iOS. Czasami kompilator AOT ma trudności z ustaleniem z wyprzedzeniem wszystkich rodzajów ogólnych, a może to być próba dostarczenia podpowiedzi.
3) Aby dodać funkcjonalność lub usunąć zależność:
Może mają określoną funkcjonalność, dla której chcą zaimplementować
StringList
(mogą być ukryte w metodzie rozszerzenia, jeszcze nie dodanej lub historycznej), do której albo nie mogą dodaćList<string>
bez dziedziczenia po niej, albo której nie chcą zanieczyszczaćList<string>
z .Alternatywnie mogą chcieć zmienić implementację w
StringList
dół ścieżki, więc na razie użyłem klasy jako znacznika (zwykle tworzysz / używasz do tego npIList
. Interfejsów ).Chciałbym również zauważyć, że ten kod został znaleziony w Irony, który jest parserem językowym - dość nietypowym rodzajem aplikacji. Może być jakiś inny konkretny powód, dla którego został użyty.
Te przykłady pokazują, że mogą istnieć uzasadnione powody, aby napisać takie oświadczenie - nawet jeśli nie jest to zbyt powszechne. Podobnie jak w przypadku innych elementów, zastanów się nad kilkoma opcjami i wybierz najlepszą dla siebie.
Jeśli spojrzysz na kod źródłowy , wydaje się, że powodem jest opcja 3 - używają tej techniki, aby dodać funkcjonalność / specjalizować kolekcje:
źródło
List<T>
jest, ale musi sprawdzićStringList
w kontekście, aby faktycznie zrozumieć, co to jest. W rzeczywistości chodzi tylkoList<string>
o inne imię. Naprawdę nie wydaje się, aby robienie tego w tak prostym przypadku było jakikolwiek semantyczny. Naprawdę zastępujesz dobrze znany typ tym samym typem o innej nazwie.Powiedziałbym, że to nie jest dobra praktyka.
List<string>
przekazuje „ogólną listę ciągów”. OczywiścieList<string>
obowiązują metody rozszerzenia. Do zrozumienia potrzebna jest mniejsza złożoność; potrzebna jest tylko lista.StringList
komunikuje „potrzebę odrębnej klasy”. Być możeList<string>
zastosowanie mają metody rozszerzenia (sprawdź faktyczną implementację typu). Potrzebna jest większa złożoność, aby zrozumieć kod; Wymagana jest znajomość implementacji listy i pochodnych klas.źródło
List<string>
.