Chociaż możemy dziedziczyć po klasie bazowej / interfejsie, dlaczego nie możemy zadeklarować List<>
używającej tej samej klasy / interfejsu?
interface A
{ }
class B : A
{ }
class C : B
{ }
class Test
{
static void Main(string[] args)
{
A a = new C(); // OK
List<A> listOfA = new List<C>(); // compiler Error
}
}
Czy jest sposób?
Odpowiedzi:
Sposobem na wykonanie tej czynności jest iteracja listy i rzutowanie elementów. Można to zrobić za pomocą ConvertAll:
Możesz również użyć Linq:
źródło
ConvertAll
czyCast
?Przede wszystkim przestań używać niemożliwych do zrozumienia nazw klas, takich jak A, B, C. Użyj zwierząt, ssaków, żyrafy, żywności, owoców, pomarańczy lub czegoś, w czym związki są jasne.
Twoje pytanie brzmi więc "dlaczego nie mogę przypisać listy żyraf do zmiennej listy typów zwierząt, skoro mogę przypisać żyrafę do zmiennej typu zwierzę?"
Odpowiedź brzmi: przypuśćmy, że możesz. Co mogło się wtedy nie udać?
Cóż, możesz dodać Tygrysa do listy zwierząt. Załóżmy, że pozwolimy ci umieścić listę żyraf w zmiennej, która zawiera listę zwierząt. Następnie próbujesz dodać tygrysa do tej listy. Co się dzieje? Czy chcesz, aby lista żyraf zawierała tygrysa? Chcesz awarię? czy chcesz, aby kompilator chronił Cię przed awarią, czyniąc przypisanie nielegalnym w pierwszej kolejności?
Wybieramy to drugie.
Ten rodzaj konwersji nazywa się konwersją „kowariantną”. W języku C # 4 umożliwimy Ci wykonanie kowariantnych konwersji na interfejsach i delegatach, gdy wiadomo, że konwersja jest zawsze bezpieczna . Zobacz moje artykuły na blogu na temat kowariancji i kontrawariancji, aby uzyskać szczegółowe informacje. (Będzie nowe na ten temat w poniedziałek i czwartek tego tygodnia).
źródło
IEnumerable
zamiast aList
? tj .:List<Animal> listAnimals = listGiraffes as List<Animal>;
nie jest możliwe, aleIEnumerable<Animal> eAnimals = listGiraffes as IEnumerable<Animal>
działa.IEnumerable<T>
iIEnumerator<T>
oba są oznaczone jako bezpieczne dla kowariancji, a kompilator to zweryfikował.Cytując wspaniałe wyjaśnienie Erica
Ale co, jeśli chcesz wybrać awarię środowiska wykonawczego zamiast błędu kompilacji? Zwykle używałbyś Cast <> lub ConvertAll <>, ale wtedy będziesz miał 2 problemy: Utworzy kopię listy. Jeśli dodasz lub usuniesz coś z nowej listy, nie zostanie to odzwierciedlone na oryginalnej liście. Po drugie, występuje duży spadek wydajności i pamięci, ponieważ tworzy nową listę z istniejącymi obiektami.
Miałem ten sam problem i dlatego utworzyłem klasę opakowania, która może rzutować listę ogólną bez tworzenia zupełnie nowej listy.
W pierwotnym pytaniu możesz użyć:
a tutaj klasa wrapper (+ metoda rozszerzająca CastList dla łatwego użycia)
źródło
Jeśli
IEnumerable
zamiast tego użyjesz , zadziała (przynajmniej w C # 4.0, nie próbowałem wcześniejszych wersji). To tylko obsada, oczywiście, nadal będzie to lista.Zamiast -
List<A> listOfA = new List<C>(); // compiler Error
W oryginalnym kodzie pytania użyj -
IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
źródło
List<A> listOfA = new List<C>(); // compiler Error
w oryginalnym kodzie pytania wpiszIEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
O ile to nie działa, pomocne może być zrozumienie kowariancji i kontrawariancji .
Aby pokazać, dlaczego to nie powinno działać, oto zmiana w podanym kodzie:
Czy to powinno działać? Pierwsza pozycja na liście jest typu „B”, ale typ elementu DerivedList to C.
Teraz załóżmy, że naprawdę chcemy stworzyć funkcję ogólną działającą na liście pewnego typu, która implementuje A, ale nie obchodzi nas, jaki to jest:
źródło
Możesz przesyłać tylko na listy tylko do odczytu. Na przykład:
I nie możesz tego zrobić dla list obsługujących zapisywanie elementów. Powodem jest:
Co teraz? Pamiętaj, że listObject i listString to w rzeczywistości ta sama lista, więc listString ma teraz element object - nie powinno to być możliwe i nie jest.
źródło
Osobiście lubię tworzyć biblioteki z rozszerzeniami klas
źródło
Ponieważ C # nie zezwala na tego typu
dziedzictwokonwersja w tej chwili .źródło
To rozszerzenie genialnej odpowiedzi BigJima .
W moim przypadku miałem
NodeBase
klasę zeChildren
słownikiem i potrzebowałem sposobu na ogólne wyszukiwanie O (1) przez dzieci. Próbowałem zwrócić pole prywatnego słownika w getterzeChildren
, więc oczywiście chciałem uniknąć kosztownego kopiowania / iteracji. Dlatego użyłem kodu Bigjima, aby rzutować naDictionary<whatever specific type>
rodzajDictionary<NodeBase>
:To działało dobrze. Jednak ostatecznie napotkałem niepowiązane ograniczenia i ostatecznie utworzyłem abstrakcyjną
FindChild()
metodę w klasie bazowej, która zamiast tego wykonywałaby wyszukiwania. Jak się okazało, wyeliminowało to przede wszystkim potrzebę korzystania z castingowego słownika. (Udało mi się go zastąpić prostymIEnumerable
do moich celów.)Więc pytanie, które możesz zadać (zwłaszcza jeśli wydajność jest problemem uniemożliwiającym korzystanie z
.Cast<>
lub.ConvertAll<>
) brzmi:„Czy naprawdę muszę rzucić całą kolekcję, czy też mogę użyć metody abstrakcyjnej, aby zachować specjalną wiedzę potrzebną do wykonania zadania, a tym samym uniknąć bezpośredniego dostępu do kolekcji?”
Czasami najprostsze rozwiązanie jest najlepsze.
źródło
Możesz również użyć
System.Runtime.CompilerServices.Unsafe
pakietu NuGet, aby utworzyć odwołanie do tego samegoList
:Biorąc pod uwagę powyższy przykład, możesz uzyskać dostęp do istniejących
Hammer
instancji na liście za pomocątools
zmiennej. DodanieTool
instancji do listy zgłaszaArrayTypeMismatchException
wyjątek, ponieważtools
odwołuje się do tej samej zmiennej cohammers
.źródło
Przeczytałem cały ten wątek i chcę tylko zwrócić uwagę na coś, co wydaje mi się niespójne.
Kompilator uniemożliwia wykonanie przypisania za pomocą list:
Ale kompilator doskonale radzi sobie z tablicami:
Spór o to, czy zadanie jest znane jako bezpieczne, rozpada się tutaj. Zadanie, które wykonałem z tablicą, nie jest bezpieczne . Aby to udowodnić, jeśli podążę za tym:
Otrzymuję wyjątek czasu wykonywania „ArrayTypeMismatchException”. Jak to wyjaśnić? Jeśli kompilator naprawdę chce mi przeszkodzić w zrobieniu czegoś głupiego, powinien uniemożliwić mi przypisanie tablicy.
źródło