2009-03-04 7 views
6

Ostatnio znalazłem bardzo zaskakujące zachowanie w języku C#. Miałem metodę, która przyjmuje jako parametr IEnumerable<Object> i przechodziłem IEnumerable<string>, ale nie jest to możliwe. Podczas gdy w języku C# wszystko może być upcast do Object niż dlaczego nie jest to możliwe? To dla mnie kompletnie zagmatwane. Proszę, oczyść mnie z tego problemu.Przesyłanie z IEnumerable <Object> do IEnumerable <string>

+0

Jak to jest niemożliwe? Czy rzuca wyjątek ArgumentException? Poza tym, jaki jest sens używania ogólnego IEnumerable, jeśli chcesz zadeklarować to jako obiekt? –

+0

To jest błąd kompilacji –

+0

To samo, co: http://stackoverflow.com/questions/6557/in-c-why-cant-a-liststring-object-be-stored-in--listobject-variable –

Odpowiedz

11

Technicznym terminem jest to, że generics są niezmienne w C# 3.0 i wcześniejszych. Od wersji C# 4.0 obsada działa.

Co niezmienne oznacza, że ​​nie ma związku między dwoma typami rodzajowymi tylko dlatego, że ich parametry rodzajowe są powiązane (tj. Są podrzędne lub nadrzędne względem siebie).

W tym przykładzie nie ma żadnej relacji między typem IEnumerable<object> i IEnumerable<string>, ponieważ łańcuch jest podtypem obiektu. Są one uważane za dwa zupełnie niezwiązane ze sobą typy, takie jak ciąg i int (nadal oba są podtypami obiektu, ale wszystko jest)

Istnieje kilka obejść i wyjątki dla tego problemu, na który natknąłeś się.

Po pierwsze, możesz rzucić każdy ciąg indywidualnie do obiektu, jeśli korzystasz z .NET 3.0, możesz to zrobić przy użyciu metody przedłużania Cast<T>(). W przeciwnym razie możesz użyć foreach i umieścić wynik w nowej zmiennej typu statycznego.

Po drugie, tablice są wyjątkiem dla typu odniesienia, tj. Przekazanie typu string [] do metody akceptującej obiekty [] powinno działać.

+1

Tylko zaktualizuj tę starszą odpowiedź, kowariancję i contravariance są teraz obsługiwane w C#, a to jest teraz prawidłowe rzutowanie. –

0

Należy używać

IEnumerable<T> 

jeśli chcesz przekazać w różnych typach, potem można wyszukać T, aby dowiedzieć się, jakiego typu jest.

0

Dobrym sposobem na przemyślenie tego jest zadać sobie pytanie "Co by się stało, gdybyś mógł to zrobić?". Weź następujący przykład:

IEnumerable<String> strings=...; 
IEnumerable<Object> objects = strings; // assume this were legal 

objects.Add(new Integer(5)); // what the... 

Właśnie dodaliśmy liczbę całkowitą do listy ciągów. Kompilator robi to, aby zachować bezpieczeństwo typu.

+0

IEnumerable nie ma jeszcze metody Add ... A w C# 4.0 faktycznie będzie można wykonać tę obsadę, ponieważ deklaracja zmieni się na IEnumerable , co pozwoli na (bezpiecznie) kontrrewaryzację. –

+0

Dobrze zamień każdą generyczną klasę, która ma metodę Add, taką jak List, następnie –

+0

-1: 'IEnumerable ' staje się 'IEnumerable ' w .NET 4 specjalnie dlatego, że twój argument ma sens dla kontenera, ale * nie * dla wyliczenia. –

3

Najprostszym sposobem przekazywania IEnumerable<string> funkcjonować wymaga IEnumerable<object> jest przez funkcję konwersji w następujący sposób:

public IEnumerable<object> convert<T>(IEnumerable<T> enumerable) 
    { 
    foreach (T o in enumerable) 
     yield return o; 
    } 

gdy C 4 wychodzi, to nie będzie neccessary, ponieważ będzie wspierać kowariancji i kontrawariancji.

+2

To jest dokładnie to, co Cast <> jest dla :) –

+0

Dzięki! Już przegłosowałem odpowiedź na tę sugestię. –

8

Jak inni podkreślili, typy generyczne są niezmienne. IEnumerable<T> może być co-wariantem, ale C# obecnie nie obsługuje określania wariantów. C# 4.0 ma obsługiwać warianty, więc może być obsługiwane w przyszłości.

Aby obejść ten problem, można teraz użyć metody LINQ: Cast<object>(). Zakładając, że masz metodę o nazwie Foo, która zajmuje IEnumerable<object>>.Możesz to tak nazwać,

Foo(stringEnumerable.Cast<object>());