2015-03-20 9 views
16

Weź to mały przykład LINQPad:Dlaczego nie obsługuje klas pomocniczych C#?

void Main() 
{ 
    Foo<object> foo = new Foo<string>(); 
    Console.WriteLine(foo.Get()); 
} 

class Foo<out T> 
{ 
    public T Get() 
    { 
     return default(T); 
    } 
} 

To nie skompilować z tego błędu:

Invalid variance modifier. Only interface and delegate type parameters can be specified as variant.

nie widzę żadnego logicznego problemu z kodem. Wszystko może być statycznie zweryfikowane. Dlaczego to nie jest dozwolone? Czy spowodowałoby to pewną niespójność w języku, czy też zostało uznane za zbyt kosztowne w implementacji z powodu ograniczenia w CLR? Jeśli to drugie, co powinienem wiedzieć jako deweloper o wspomnianym ograniczeniu?

Biorąc pod uwagę, że interfejsy obsługują to, oczekiwałbym wsparcia klasy, aby logicznie z tego wynikać.

+0

Wynika to z ograniczenia CLR. Teraz byłoby fajnie, gdybyś zmodyfikował swoje pytanie, aby ktoś mógł odpowiedzieć na pytanie, dlaczego istnieje ograniczenie CLR. – Stilgar

+4

@Stilgar: Nie mógłbyś odpowiedzieć "Nie jest to obsługiwane z powodu tej rzeczy w podstawowej implementacji CLR"? – Chris

+1

Klasy muszą martwić się szczegółami implementacji - delegaci i interfejsy tego nie robią.Wykonanie tej pracy wymagałoby wiele pracy. Pytanie, dlaczego nikt z zespołu C# nie wykonał tej pracy, wydaje się nieco bezsensowne - nie, nie jest wspierane, koniec historii. –

Odpowiedz

8

Jednym z powodów może być:

class Foo<out T> 
{ 
    T _store; 
    public T Get() 
    { 
    _store = default(T); 
    return _store; 
    } 
} 

Ta klasa zawiera funkcję, która nie jest kowariantna, ponieważ ma pole i pola mogą być ustawione na wartości. Jest on jednak wykorzystywany w sposób kowariancyjny, ponieważ przypisuje mu się tylko wartość domyślną i tylko w przypadku każdego przypadku, w którym faktycznie używana jest kowariancja, będzie ona dostępna tylko w jednym przypadku.

Jako takie nie jest jasne, czy możemy na to pozwolić. Niewykonanie tego mogłoby zirytować użytkowników (w końcu pasuje do tych samych potencjalnych zasad, które sugerujesz), ale pozwolenie na to jest trudne (analiza jest już trochę trudna i nie jesteśmy nawet w stanie zacząć polować na naprawdę podchwytliwe przypadki).

Z drugiej strony, analiza ta jest znacznie prostsza:

void Main() 
{ 
    IFoo<object> foo = new Foo<string>(); 
    Console.WriteLine(foo.Get()); 
} 

interface IFoo<out T> 
{ 
    T Get(); 
} 

class Foo<T> : IFoo<T> 
{ 
    T _store; 
    public T Get() 
    { 
    _store = default(T); 
    return _store; 
    } 
} 

Jest to łatwe do ustalenia, że ​​żaden z realizacji IFoo<T> przerw kowariancja, ponieważ nie ma żadnych. Wszystko, co konieczne, to upewnienie się, że nie ma potrzeby używania parametru T jako parametru (w tym parametru metody ustawiającej) i gotowe.

Fakt, że potencjalne ograniczenie jest o wiele bardziej uciążliwe dla klasy niż z interfejsu z podobnych powodów, również zmniejsza stopień użyteczności klas kowariancji. Z pewnością nie byłyby one bezużyteczne, ale równowaga tego, jak użyteczne byłyby one nad tym, ile pracy będzie polegało na określeniu i wdrożeniu zasad dotyczących tego, co mogliby zrobić, jest znacznie mniejsza niż równowaga między tym, jak użyteczne są kowariantne interfejsy nad tym, jak dużo pracy trzeba było określić i wdrożyć.

Z pewnością różnica jest wystarczająca, że ​​minęło punkt "dobrze, jeśli masz zamiar pozwolić X, byłoby głupie, aby nie pozwolić Y ...".

+0

Bardzo dobry przykład. Domyślam się, że jeśli mój przykład zadziałał, ludzie mogliby oczekiwać, że twój przykład również zadziała? Widzę, jak może to powodować pewne bóle głowy dla projektantów języków. –

+0

Tak. Musieliby pozwolić, aby mój przykład zadziałał (co jest znacznie trudniejszą analizą, która musi uwzględniać poziomy dostępu itd.) Lub ładnie wyjaśnić, dlaczego tak nie jest. A im więcej nie pozwalamy na pracę, tym mniej programistów może używać tej wariancji. Jak już powiedziałem, z pewnością nie byłoby to bezużyteczne (na początku może być bardzo przyjemne w wielu niezmiennych przypadkach), ale koszty i korzyści zdecydowanie różnią się od tych w przypadku innych interfejsów. –

0

Klasa musi zawierać tylko parametry metody wyjściowej (w celu kowariantności) i tylko parametry metody wejściowej (w celu zachowania kontrawariancji). Chodzi o to, że trudno jest zagwarantować, że dla klas: na przykład, klasa kowariantowa (według parametru typu T) nie może mieć pól T, ponieważ możeszpisać do tych pól. Byłoby to świetne rozwiązanie dla naprawdę niezmiennych klas, ale w tej chwili nie ma kompleksowego wsparcia dla niezmienności w C# (powiedzmy, jak w Scali).

+2

Można to łatwo zweryfikować podczas kompilacji, tak jak w moim przykładzie. –

+1

Nie jest łatwo zagwarantować, że bez głębokiego niezmienności wsparcie w języku. Zobacz link do doskonałej odpowiedzi Eric Lippert na [podobne pytanie] (http://stackoverflow.com/questions/2733346/why-isnt-there-generic-variance-for-classes-in-c-sharp-4-0). – ialekseev

+0

Niezmienność nie jest wymagana. Na przykład każde publiczne pole typu T byłoby naruszeniem reguł, a T musiałoby być niezmienne. Może być kilka sytuacji, w których zastosowanie ma wariancja, ale mogą one nadal istnieć. –

Powiązane problemy