2012-12-11 13 views
27

Próbuję rzucić contravariant delegata, ale z jakiegoś powodu mogę to zrobić tylko za pomocą operatora "as".Mogę rzucić tylko contravariant delegata z "jak"

interface MyInterface { } 
delegate void MyFuncType<in InType>(InType input); 

class MyClass<T> where T : MyInterface 
{ 
    public void callDelegate(MyFuncType<MyInterface> func) 
    { 
     MyFuncType<T> castFunc1 = (MyFuncType <T>) func; //Error 
     MyFuncType<T> castFunc2 = func as MyFuncType<T>; 
     MyFuncType<T> castFunc3 = func is MyFuncType<T> ? (MyFuncType<T>)func : (MyFuncType<T>)null; //Error 
    } 
} 

castFunc2 działa dobrze, ale castFunc1 i castFunc3 powodować błąd:

Cannot convert type 'delegateCovariance.MyFuncType<myNamespace.MyInterface>' to myNamespace.MyFuncType<T>' 

The MSDN article on the as operator stany że castFunc2 i castFunc3 są "równoważne", więc nie rozumiem, jak tylko jeden z nich może spowodować błąd. Innym elementem, który wprowadza mnie w błąd, jest to, że zmiana MyInterface z interfejsu na klasę pozwala pozbyć się błędu.

Czy ktoś może mi pomóc zrozumieć, co się tutaj dzieje? Dzięki!

+2

Kompiluje się dobrze dla mnie tutaj: http://ideone.com/5SjUxV Czy brakuje mi czegoś? (nie najbardziej znany z ideałem). Otrzymuję określony błąd, jeśli usunę "in" z deklaracji 'MyFuncType'. –

+0

hmm. Pomyślałem, że być może nie dostałeś erros, ponieważ ideone używa mono, ale próbowałem używać mono z visual studio i wciąż dostaję błędy. – rob

+0

@rob Użyłem kierowania monofonicznego .Net 4 i nie otrzymałem błędów. Którą wersją środowiska .Net kierujesz? –

Odpowiedz

15

Dodaj ograniczenie tak, że T musi być klasą.

class MyClass<T> where T: class, MyInterface 

Daje to kompilatorowi wystarczającą ilość informacji, aby wiedzieć, że T jest zamienne. Nie potrzebujesz też wyraźnej obsady.

Odchylenie ma zastosowanie tylko do typów odniesienia. T może być typem wartości bez ograniczenia, które przerywa kompilatorom możliwość udowodnienia, że ​​T jest kompatybilny z kontrawariancją.

Powód, dla którego działa drugie stwierdzenie, polega na tym, że as faktycznie może wykonać zerową konwersję. Na przykład:

class SomeClass { } 
interface SomeInterface { } 
static void Main(string[] args) 
{ 
    SomeClass foo = null; 
    SomeInterface bar = foo as SomeInterface; 
} 

Foo nie jest oczywiście bezpośrednio convertable do SomeInterface, ale nadal udaje bo null konwersja może jeszcze nastąpić. Twoja referencja MSDN może być poprawna dla większości scenariuszy, ale wygenerowany kod IL jest bardzo różny, co oznacza, że ​​są zasadniczo różne od technicznej perspektywy.

+1

+1. Możesz również zrobić to, aby uniknąć błędu kompilatora: 'MyFuncType castFunc1 = (MyFuncType ) (obiekt) func;' ale to będzie wysadzać w czasie wykonywania dla typów wartości, tak więc to jest lepsze rozwiązanie. –

+1

** Dobre wyjaśnienie. ** Odnosząc się do twojego ostatniego przykładu: Tutaj, bez żadnych generycznych, również kompilowałaby się jawna obsada. To znaczy, że 'bar = (SomeInterface) foo' będzie dozwolone podczas kompilacji. Jednak w przypadku ogólnego parametru, takiego jak 'T', kompilator jest bardziej restrykcyjny. Ma to na celu pomóc ludziom nie nadużywać generycznych środków i wykonywać nadmierne rzucanie. Zauważ w swoim przykładzie 'foo' /' bar', że bardzo ważne jest, aby 'SomeClass' nie był zapakowany. Jeśli byłby "zapieczętowany", kompilator wiedziałby, że 'SomeClass' nigdy nie może być' SomeInterface'. –

-1

Twoja klasa mówi, że T implementuje MyInterface, ponieważ MyInterface nie jest typem instancji. Dlatego nie można zagwarantować, że MyFuncType<T> będzie MyFuncType<MyInterface>. Może to być MyFuncType<SomeType> i SomeType : MyInterface, ale to nie będzie to samo, co SomeOtherType : MyInterface. Ma sens?

+0

"' MyFuncType 'nie ma gwarancji, że będzie to' MyFuncType '" To prawda, ale myślę, że mylicie casting z zadaniem. –

+0

Dzięki za edycję. Nie zdawałem sobie sprawy, że to zepsułem. Tutaj nie ma zamieszania związanego z castingiem i zadaniami. Chodzi mi o to, że generics to szablony, a 'T' nigdy nie będzie gwarantowane jako' MyInterface'. – Jake

4

Eric Lippert dał świetne wyjaśnienie tego problemu w swoich ostatnich postach: An "is" operator puzzle, part one, An "is" operator puzzle, part two.

Główna przyczyna tego zachowania jest następująca: operatory "są" (lub "jak") to nie to samo, co operatory. Operator "as" może wywołać niezerowe zdarzenie wynikowe, jeśli odpowiedni rzut okaże się niezgodny z prawem, co jest szczególnie ważne, gdy mamy do czynienia z argumentami typu.

Zasadniczo cast operator w przypadku oznacza (jak powiedział Eric), który „Wiem, że wartość ta jest od danego typu, choć kompilator nie wie, że kompilator powinno to pozwolić” lub " Wiem, że ta wartość nie jest podana w danym typie:; należy wygenerować specjalny kod specyficzny dla danego typu, aby przekonwertować wartość jednego typu na wartość innego typu."

Później sprawa dotyczy konwersji wartości, takich jak podwójne do całkowitej przemiany i możemy ignorować tego znaczenia w obecnym kontekście.

i generycznych typu argumenty nie są logiczne w kontekście pierwszego nie. Jeśli masz do czynienia z ogólnym rodzajem argumentu, dlaczego nie określasz wyraźnie tego "kontraktu", używając ogólnego argumentu typu?

Nie jestem w 100% pewien, co chcesz osiągnąć, ale możesz pominąć specjalny typ od do metody i swobodnie użyj ogólnego argumentu:

class MyClass<T> where T : MyInterface 
{ 
    public void callDelegate(Action<T> func) 
    { 
    } 
} 

class MyClass2 
{ 
    public void callDelegate<T>(Action<T> func) 
     where T : MyInterface 
    { 
    } 
} 

W przeciwnym razie powinieneś użyć operatora as z kontrolą zerową zamiast sprawdzania typu.

Powiązane problemy