2010-01-03 10 views
15

Czytałem wywiad z Joshua Blochem w Coders at Work, gdzie ubolewał nad wprowadzeniem generycznych w Javie 5. Nie podoba mu się konkretna implementacja w dużej mierze dlatego, że obsługa wariancji - jałowe karty Java - sprawia, że ​​jest ona niepotrzebnie skomplikowana.C# generics - bez dolnych granic według projektu?

Z tego co wiem, C# 3 nie ma nic takiego jak wyraźne, ograniczone symbole wieloznaczne, np. nie można zadeklarować metody PriceBatch, która pobiera kolekcję zasobów lub dowolnej podklasy Asset (void PriceBatch(Collection<? extends Asset> assets) w Javie?).

Czy ktoś wie dlaczego symbole wieloznaczne i ograniczenia nie zostały dodane do C#? Czy te funkcje zostały celowo pominięte, aby uczynić język prostszym, czy jest to coś, czego po prostu nie udało się jeszcze wdrożyć?

EDYCJA: Święty dym, komentarze samego Erica Lipperta! Po przeczytaniu jego i wnikliwe komentarze Pawła, zdaję sobie sprawę, że przynajmniej górne granice są obsługiwane i że powyższy przykład można przetłumaczyć do C# jak:

void PriceBatch<T>(ICollection<T> assets) where T : Asset 

dolnych granic, z drugiej strony, są najwyraźniej nie jest obsługiwany jako Eric mówi w swoim drugim komentarzu, np prawdopodobnie nie ma możliwości bezpośredniego przetłumaczenia tego (nieco spreparowanego) kodu Java na C#:

public class Asset {} 
public class Derivative extends Asset {} 
public class VanillaOption extends Derivative {} 

public static <T extends Asset> void copyAssets(Collection<T> src, Collection<? super T> dst) { 
    for(T asset : src) dst.add(asset); 
} 

Collection<VanillaOption> src = new ArrayList<VanillaOption>(); 
[...] 
Collection<Derivative> dst = new ArrayList<Derivative>(); 
[...] 
copyAssets(src, dst); 

Czy mam rację? Jeśli tak, to czy istnieje szczególny powód, dla którego C# ma górne, ale nie niższe granice?

+5

Nie rozumiem pytania; C# ma ograniczone generyczne i od C# 2. Czy to, co uzyskujesz w tym języku, obsługuje * kowariancję i kontrawariancję * call-site * na skonstruowanych rodzajach ogólnych? Ponieważ C# nie obsługuje tego; C# 4 będzie obsługiwać * wariancję deklaracji-witryny *. –

+6

Czy to, co robisz, polega na tym, że granice parametrów typu na ogólnych metodach są potrzebne tylko od granic, a nie od granic? C# nie obsługuje tego drugiego; Java i Scala wierzę. –

+0

Dzięki, zaktualizowałem pytanie. – ehnmark

Odpowiedz

20

Skomplikowane pytanie.

Najpierw zastanówmy się nad podstawowym pytaniem: "dlaczego jest to nielegalne w języku C#?„

class C<T> where T : Mammal {} // legal 
class D<T> where Giraffe : T {} // illegal 

Oznacza to, że ogólny typ Ograniczenie może powiedzieć«T musi być każdy rodzaj odniesienia, który może być przypisany do zmiennej typu ssak», ale nie” T musi być dowolnego typu odniesienia, zmienną, która może być przypisana żyrafa ".Dlaczego ta różnica?

Nie wiem, to było na długo przed moim czasem w zespole C# Odpowiedź była banalna:" ponieważ CLR jej nie obsługuje ", ale zespół to zaprojektował C# generics był ten sam zespół, który zaprojektował generics CLR, więc to naprawdę nie jest dużo wyjaśnienia.Domyślam się, że jak zwykle, aby być obsługiwanym, funkcja musi być zaprojektowana, wdrożona, przetestowana, udokumentowana i wysłana do klientów; nikt nigdy nie zrobił żadnej z tych rzeczy dla tej cechy, a zatem nie jest w języku. Nie widzę dużej, nieodpartej korzyści dla proponowanej funkcji; skomplikowane funkcje bez przekonujących korzyści są tu często ograniczane.

Jednak to domysły. Następnym razem, gdy rozmawiam z chłopakami, którzy pracowali na lekach generycznych - mieszkają w Anglii, więc nie jest tak, że są tylko w przedpokoju ode mnie, niestety - zapytam.

Co do twojego konkretnego przykładu, myślę, że Paul ma rację. Nie potrzebujesz mniejszych wiązań ograniczających, aby działało to w języku C#. Można powiedzieć:

void Copy<T, U>(Collection<T> src, Collection<U> dst) where T : U 
{ 
    foreach(T item in src) dst.Add(item); 
} 

Oznacza to, umieścić ograniczenia na T, a nie na U.

+1

Dzięki za kontynuację.Książka Effective Java wyjaśnia scenariusz, w którym byłoby to użyteczne: powiedzmy, że zdefiniowałeś typ 'MyStack ' z metodą 'PopAll', która pobiera zbiór docelowy jako parametr. Jeśli masz 'MyStack ' powinieneś być w stanie skopiować jego elementy do 'Listy ', ale aby wyrazić, że będziesz musiał powiedzieć coś w rodzaju pustki publicznej 'PopAll (ICollection dst) gdzie T: X' i to wydaje się nielegalne. – ehnmark

+7

Tak, to byłoby przydatne. Co ciekawe, w języku C#, chociaż nie możesz tego zrobić bezpośrednio, możesz * to zrobić *, tworząc metodę rozszerzenia na stosie ! Wprowadza nowy parametr, który można następnie ograniczyć. –

+0

Innym przykładem, jaki mogłem zobaczyć, byłaby kowariancja 'IImmutableList ' w 'System.Collections.Immutable'; obecnie 'IImmutableList Add (T item)' zapobiega temu, ale 'IImmutableList Add (element TNew), gdzie TNew: T' by go zapisać. (No cóż, na początku nie implementowałoby się zmiennego interfejsu na niezmiennym typie, ale przypuszczam, że mieli powody, by pójść z tym projektem.) W tym przypadku metody rozszerzania nie doprowadzą was tak daleko. – fuglede

8

C# 4 wprowadza nowe funkcje, które umożliwiają kowariancję i kontrrawariancje w przypadku leków generycznych.

Istnieją inne SO posty, które mówią o tym bardziej szczegółowo: How is Generic Covariance & Contra-variance Implemented in C# 4.0?

Nowa funkcja nie to automatycznie włączyć w wszystkich typów, ale nie jest to nowa składnia, który pozwala programistom na określenie czy generyczne argumenty są kowariant lub kontrawariant.

Wersje C# sprzed C# 4 miały ograniczoną funkcjonalność podobną do tej, która dotyczy delegatów i niektórych typów tablic. W odniesieniu do delegatów dopuszcza się delegatów przyjmujących podstawowe typy parametrów. Jeśli chodzi o typy tablic, myślę, że jest to poprawne, chyba że w grę wchodzi boks. Oznacza to, że tablica klienta może być przykładem dla tablicy obiektów. Jednak tablicy int nie można rzutować na tablicę obiektów.

+0

Tablice też: String [] jest również obiektem []. – codekaizen

+0

Zaktualizowałem odpowiedź, dzięki. – Eilon

+2

Zmienne kolekcje/tablice muszą być nie-wariantowe. W przeciwnym razie łamie bezpieczeństwo typu. –

7

.NET ma już równowartość symboli wieloznacznych, bardziej logicznie nazwie ograniczenia typu rodzajowego, można zrobić to, co można opisać bez problemów

namespace ConsoleApplication3 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
     List<a> a = new List<a>(); 
     List<b> b = new List<b>(); 
     List<c> c = new List<c>(); 
     test(a); 
     test(b); 
     test(c); 

     } 

     static void test<T>(List<T> a) where T : a 
     { 
      return; 
     } 
    } 
    class a 
    { 

    } 
    class b : a 
    { 

    } 
    class c : b 
    { 

    } 
} 

Przykład 2

namespace ConsoleApplication3 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      ICollection<VanillaOption> src = new List<VanillaOption>(); 
     ICollection<Derivative> dst = new List<Derivative>(); 
     copyAssets(src, dst); 
     } 

     public static void copyAssets<T,G>(ICollection<T> src, ICollection<G> dst) where T : G { 
      foreach(T asset in src) 
       dst.Add(asset); 
     } 
    } 
    public class Asset {} 
    public class Derivative : Asset {} 
    public class VanillaOption : Derivative {} 
} 

Ten przykład przedstawia konwersja kodu z twojego przykładu w java.

Nie jestem jednak w stanie odpowiedzieć na prawdziwe pytanie!

+0

Cześć Paul, pytanie: implikacja twojego przykładu kodu jest taka, że ​​ogólne ograniczenie wyrażone w "where T: a" ... z racji tego, że będzie "obsługiwać" nie tylko instancję samego "a", ale także, każdy obiekt zstępujący, bez względu na to, jak "pośrednio" z "a": jest odpowiednikiem symboli wieloznacznych w generycznych w Javie? Po prostu staram się wyjaśnić dla mojej własnej korzyści; nie ma żadnej krytyki twojej domniemanej lub zamierzonej odpowiedzi. Fakt, że litera "a" ma trzy różne znaczenia (klasa, zmienna wewnętrzna i nazwa parametru) w twoim przykładzie utrudniał mi grok, fyi. – BillW

+0

Tak, to jest trochę niejasne, przepraszam. Napisałem przykład twojego dolnego ograniczonego kodu, funkcjonalnie jest taki sam, ale nie używa niższych granic! –

+0

Cześć Paul i dzięki za zaktualizowaną odpowiedź. Moje pytanie brzmi, dlaczego C# 3 ma tylko podzbiór wsparcia wariancji Javy pod tym względem i czy jest to celowa decyzja, aby zrobić "dobrze" to, co Java "źle" zrobiła lub ma do czynienia z ograniczeniami CLR lub czymkolwiek innym. Trudno jest znaleźć sensowny przykład, kiedy dolne granice byłyby użyteczne, jak ilustruje to mój wymyślny przykład, a może to część odpowiedzi :) – ehnmark