W C # dlaczego nie można przechowywać obiektu List <string> w zmiennej List <object>

85

Wygląda na to, że obiekt List nie może być przechowywany w zmiennej List w C #, a nawet nie może być jawnie rzutowany w ten sposób.

List<string> sl = new List<string>();
List<object> ol;
ol = sl;

powoduje, że nie można niejawnie przekonwertować typu System.Collections.Generic.List<string>naSystem.Collections.Generic.List<object>

I wtedy...

List<string> sl = new List<string>();
List<object> ol;
ol = (List<object>)sl;

powoduje, że nie można przekonwertować typu System.Collections.Generic.List<string>naSystem.Collections.Generic.List<object>

Oczywiście możesz to zrobić, wyciągając wszystko z listy ciągów i umieszczając je z powrotem po jednym, ale jest to dość zawiłe rozwiązanie.

Matt Sheppard
źródło
5
To się zmieni w C # 4.0, więc możesz sprawdzić kowariancję i kontrawariancję. Pozwoli to na takie rzeczy w bezpieczny sposób.
John Leidegren
Mniej więcej duplikat: stackoverflow.com/questions/317335/…
Joel in Gö
7
John> C # 4 nie pozwoli na to. Pomyśl o ol.Add (new object ());
Guillaume
Odpowiedział na nie Jon Skeet tutaj: stackoverflow.com/a/2033921/1070906
JCH2k

Odpowiedzi:

39

Pomyśl o tym w ten sposób, jeśli miałbyś wykonać takie rzutowanie, a następnie dodać obiekt typu Foo do listy, lista łańcuchów nie jest już spójna. Gdybyś miał iterować pierwsze odwołanie, dostałbyś wyjątek rzutowania klasy, ponieważ po trafieniu na instancję Foo, Foo nie może zostać przekonwertowane na łańcuch!

Na marginesie, myślę, że byłoby bardziej istotne, czy możesz wykonać obsadę odwrotną:

List<object> ol = new List<object>();
List<string> sl;
sl = (List<string>)ol;

Od jakiegoś czasu nie używałem C #, więc nie wiem, czy to jest legalne, ale ten rodzaj rzutowania jest faktycznie (potencjalnie) przydatny. W tym przypadku przechodzisz od bardziej ogólnej klasy (obiektu) do bardziej szczegółowej klasy (łańcucha), która rozciąga się od ogólnej. W ten sposób, jeśli dodajesz do listy ciągów, nie naruszasz listy obiektów.

Czy ktoś wie lub może sprawdzić, czy taka obsada jest legalna w C #?

Mike Stone
źródło
4
Myślę, że twój przykład cierpi na ten sam problem. Co się stanie, jeśli ol zawiera coś, co nie jest ciągiem? Problem polega na tym, że niektóre metody na liście działałyby dobrze, takie jak dodawanie / wstawianie. Ale iteracja może być prawdziwym problemem.
Chris Ammerman,
3
Eric Lippert opublikował świetną serię postów na blogu na ten temat: dlaczego dodanie ograniczeń kowariancji i kontrawariancji do metod ogólnych może nie działać tak, jak byśmy chcieli na poziomie klasy. is.gd/3kQc
Chris Ammerman,
@ChrisAmmerman: Gdyby olbyło w nim coś, co nie jest ciągiem, przypuszczam, że spodziewałbym się niepowodzenia rzutowania w czasie wykonywania. Ale naprawdę miałbyś kłopoty, gdyby rzut się powiódł, a potem dodano coś ol, co nie jest ciągiem. Ponieważ slodwołuje się do tego samego obiektu, teraz twój List<string>będzie zawierał inny niż ciąg. To Addjest problem, który, jak sądzę, uzasadnia, dlaczego ten kod się nie kompiluje, ale skompiluje się, jeśli zmienisz List<object> olna IEnumerable<object> ol, który nie ma rozszerzenia Add. (Sprawdziłem to w C # 4)
Tim Goodman
Po tej zmianie kompiluje się, ale zgłasza, InvalidCastExceptionponieważ typ środowiska wykonawczego oljest nadal List<object>.
Tim Goodman,
36

Jeśli używasz .NET 3.5, zapoznaj się z metodą Enumerable.Cast. Jest to metoda rozszerzenia, więc możesz ją wywołać bezpośrednio na liście.

List<string> sl = new List<string>();
IEnumerable<object> ol;
ol = sl.Cast<object>();

Nie jest to dokładnie to, o co prosiłeś, ale powinno załatwić sprawę.

Edycja: jak zauważyła Zooba, możesz następnie wywołać ol.ToList (), aby uzyskać listę

Promień
źródło
14

Nie można rzutować między typami ogólnymi z różnymi parametrami typu. Wyspecjalizowane typy ogólne nie stanowią części tego samego drzewa dziedziczenia, a więc są to typy niepowiązane.

Aby to zrobić przed NET 3.5:

List<string> sl = new List<string>();
// Add strings to sl

List<object> ol = new List<object>();

foreach(string s in sl)
{
    ol.Add((object)s);  // The cast is performed implicitly even if omitted
}

Korzystanie z Linq:

var sl = new List<string>();
// Add strings to sl

var ol = new List<object>(sl.Cast<object>());

// OR
var ol = sl.Cast<object>().ToList();

// OR (note that the cast to object here is required)
var ol = sl.Select(s => (object)s).ToList();
Zooba
źródło
11

Powodem jest to, że klasa ogólna, taka jak, List<>jest w większości celów traktowana zewnętrznie jako klasa normalna. np. kiedy mówisz, List<string>()że kompilator mówi ListString()(który zawiera ciągi znaków). [Techniczni ludzie: to jest skrajnie zrozumiała angielska wersja tego, co się dzieje]

W związku z tym oczywiście kompilator nie może być wystarczająco inteligentny, aby przekonwertować ListString na ListObject przez rzutowanie elementów jego wewnętrznej kolekcji.

Dlatego istnieją metody rozszerzające dla IEnumerable, takie jak Convert (), które umożliwiają łatwe przekształcanie elementów przechowywanych w kolekcji, co może być tak proste, jak rzutowanie między nimi.

Rex M
źródło
6

Ma to wiele wspólnego z kowariancją, np. Typy ogólne są uważane za parametry, a jeśli parametry nie są poprawnie rozwiązywane na bardziej szczegółowy typ, operacja kończy się niepowodzeniem. Konsekwencją takiego rozwiązania jest to, że naprawdę nie można rzutować na bardziej ogólny typ, taki jak obiekt. Jak stwierdził Rex, obiekt List nie skonwertuje każdego obiektu za Ciebie.

Zamiast tego możesz wypróbować kod FF:

List<string> sl = new List<string>();
//populate sl
List<object> ol = new List<object>(sl);

lub:

List<object> ol = new List<object>();
ol.AddRange(sl);

ol bez problemu skopiuje całą zawartość sl.

Jon Limjap
źródło
5

Tak, możesz od .NET 3.5:

List<string> sl = new List<string>();
List<object> ol = sl.Cast<object>().ToList();
Tamas Czinege
źródło
1

To jest właściwie, aby nie próbować umieszczać żadnego dziwnego "obiektu" w swoim wariancie listy "ol" (jak List<object>mogłoby się wydawać) - ponieważ wtedy twój kod by się wywalił (ponieważ lista naprawdę jest List<string>i będzie akceptować tylko obiekty typu String ). Dlatego nie możesz rzutować swojej zmiennej na bardziej ogólną specyfikację.

W Javie jest odwrotnie, nie masz typów ogólnych, a zamiast tego wszystko jest Listą obiektów w czasie wykonywania, a naprawdę możesz umieścić dowolny dziwny obiekt na swojej rzekomo ściśle wpisanej Liście. Wyszukaj „Reified generics”, aby zobaczyć szerszą dyskusję na temat problemu Java ...

Valters Vingolds
źródło
1

Taka kowariancja w przypadku typów ogólnych nie jest obsługiwana, ale w rzeczywistości można to zrobić z tablicami:

object[] a = new string[] {"spam", "eggs"};

C # wykonuje testy w czasie wykonywania, aby zapobiec umieszczaniu, powiedzmy, intdo a.

Constantin
źródło
0

Oto inne rozwiązanie pre- .NET 3,5 dla każdego IList, którego zawartość może być rzutowana niejawnie.

public IList<B> ConvertIList<D, B>(IList<D> list) where D : B
{
    List<B> newList = new List<B>();

    foreach (D item in list)
    {
        newList.Add(item);
    }

    return newList;
}

(Na podstawie przykładu Zooby)

Alvin Ashcraft
źródło
0

Mam:

private List<Leerling> Leerlingen = new List<Leerling>();

Miałem zamiar wypełnić go danymi zebranymi w tym, List<object> co w końcu zadziałało dla mnie:

Leerlingen = (List<Leerling>)_DeserialiseerLeerlingen._TeSerialiserenObjecten.Cast<Leerling>();

.Castto od rodzaju chcesz uzyskać IEnumerablez tego typu, a następnie typecast IEnemuerabledo List<>chcesz.

Menno
źródło
0

Mm, dzięki wcześniejszym komentarzom znalazłem dwa sposoby, aby się tego dowiedzieć. Pierwszym z nich jest pobranie listy elementów, a następnie rzutowanie jej na listę obiektów IEnumerable:

IEnumerable<object> ob;
List<string> st = new List<string>();
ob = st.Cast<object>();

Drugim jest unikanie typu obiektu IEnumerable, po prostu rzutowanie ciągu na typ obiektowy, a następnie użycie funkcji „toList ()” w tym samym zdaniu:

List<string> st = new List<string>();
List<object> ob = st.Cast<object>().ToList();

Drugi sposób bardziej mi się podoba. Mam nadzieję, że to pomoże.

Cristinadeveloper
źródło
0
List<string> sl = new List<string>();
List<object> ol;
ol = new List<object>(sl);
Kozen
źródło