W jaki sposób generyczna kowariancja i kontrawariancja jest implementowana w C # 4.0?

106

Nie uczestniczyłem w PDC 2008, ale usłyszałem wiadomości, że C # 4.0 ma obsługiwać kowariancję i kontrwariancję rodzajową. Oznacza to, że List<string>można przypisać do List<object>. Jak to możliwe?

W książce Jona Skeeta C # in Depth wyjaśniono, dlaczego typy generyczne języka C # nie obsługują kowariancji i kontrawariancji. Służy głównie do pisania bezpiecznego kodu. Teraz zmieniono C # 4.0, aby je obsługiwał. Czy przyniesie chaos?

Czy ktoś zna szczegóły dotyczące języka C # 4.0, może udzielić wyjaśnienia?

Morgan Cheng
źródło
Oto dobry artykuł, który obejmuje nadchodzące implementacje kowariancji i kontrawariancji
CMS
Anders Noråse wyjaśnia w C # 4.0 - kowariancja i przeciwwariancja koncepcję i pokazuje, że jest ona obsługiwana już dzisiaj w IL od .NET 2.0.
Thomas Freudenberg

Odpowiedzi:

155

Wariancja będzie obsługiwana tylko w bezpieczny sposób - w rzeczywistości przy użyciu umiejętności, które już posiada CLR. Tak więc przykłady, które podam w książce, jak próbować użyć a List<Banana>jako List<Fruit>(lub cokolwiek to było), nadal nie będą działać - ale kilka innych scenariuszy będzie działać.

Po pierwsze, będzie obsługiwane tylko w przypadku interfejsów i delegatów.

Po drugie, wymaga od autora interfejsu / delegata dekoracji parametrów typu jako in(dla kontrawariancji) lub out(dla kowariancji). Najbardziej oczywistym przykładem jest to, IEnumerable<T>że pozwala tylko „wyjąć” z niego wartości - nie pozwala na dodawanie nowych. To się stanie IEnumerable<out T>. To wcale nie szkodzi bezpieczeństwu typów, ale pozwala na przykład zwrócić wartość IEnumerable<string>z metody zadeklarowanej do zwrócenia IEnumerable<object>.

Kontrawariancja jest trudniejsza do podania konkretnych przykładów użycia interfejsów, ale z delegatem jest to łatwe. Rozważ Action<T>- to po prostu reprezentuje metodę, która przyjmuje Tparametr. Byłoby miło móc bezproblemowo konwertować, używając Action<object>jako an Action<string>- każda metoda, która przyjmuje objectparametr, będzie w porządku, gdy zostanie przedstawiona stringzamiast. Oczywiście C # 2 ma już w pewnym stopniu kowariancję i kontrawariancję delegatów, ale poprzez rzeczywistą konwersję z jednego typu delegata na inny (tworzenie nowej instancji) - zobacz przykłady w P141-144. C # 4 uczyni to bardziej ogólnym i (wierzę) pozwoli uniknąć tworzenia nowej instancji konwersji. (Zamiast tego będzie to konwersja referencyjna).

Mam nadzieję, że to trochę wyjaśnia - daj mi znać, jeśli to nie ma sensu!

Jon Skeet
źródło
3
Czy to oznacza, że ​​jeśli klasa jest zadeklarowana jako „List <out T>”, to NIE powinna mieć funkcji składowej, takiej jak „void Add (T obj)”? Kompilator C # 4.0 zgłosi błąd, prawda?
Morgan Cheng,
1
Morgan: Z pewnością tak rozumiem, tak.
Jon Skeet
4
po raz kolejny jedna z twoich odpowiedzi na SO natychmiast pomogła mi ulepszyć kod. Dziękuję Ci!
Mark
@ Ark-kun: Tak, jestem tego świadomy. Stąd „nadal nie zadziała” w tym samym zdaniu. (I jestem też świadomy powodów.)
Jon Skeet,
@JonSkeet Czy to prawda, że ​​„możesz używać tylko List<Banana>jako IList<Fruit>”, jak powiedział @ Ark-kun? Jeśli tak, jak to możliwe, chociaż parametr typu IList<T>interfejsu nie jest zdefiniowany jako kowariantny (nie out T, ale po prostu T).
gehho,