2013-03-24 9 views
5

Nie jestem pewien, co się tutaj dzieje, ale dostaję błąd kompilatora przy użyciu następującego kodu:C# rodzajowych Interfejs kowariancji

namespace SO 
{ 
    interface IUser<PostType> 
    { 
     PostType Post { get; set; } 
    } 

    interface IPost<UserType> 
    { 
     UserType User { get; set; } 
    } 

    class User : IUser<Post> 
    { 
     //Implementation 
    } 

    class Post : IPost<User> 
    { 
     //Implementation 
    } 

    class SomeOtherClass 
    { 
     // Compiler Error: Cannot implicitly convert type 'SO.User' to 
     // 'SO.IUser<SO.IPost<SO.User>>'. An explicit conversion exists 
     // (are you missing a cast?) 
     IUser<IPost<User>> user = new User(); 

     //Works Fine 
     IUser<Post> user = new User(); 
    } 
} 

Dlaczego otrzymuję błąd jeśli Post jest podtypem IPost<User>? Wiem, że w tym przypadku mógłbym po prostu użyć User zamiast IUser<IPost<User>>, ale chcę wiedzieć, dlaczego to nie działa.

+0

Możesz zacząć [tutaj ] (http://blogs.msdn.com/b/ericlippert/archive/2007/10/26/covariance-and-contravariance-in-c-part-five-interface-variance.aspx) –

Odpowiedz

12

postaram się wyjaśnić za pomocą prostego przykładu. Załóżmy, że masz jeszcze jedną klasę wykonawczą IPost<User>:

class PicturePost : IPost<User> 
{ 
    // Implementation 
} 

Wtedy ten kod nie zostanie skompilowany:

IUser<Post> user = new User(); 
    user.Post = new PicturePost(); 

Ponieważ user.Post jest z betonu klasy Post, który nie jest kompatybilny z PicturePost (są rodzeństwem).

Następnie wyobraź sobie, że linia z pytaniem został pomyślnie skompilowany:

// C# compiler is so kind today and it compiled this. 
    IUser<IPost<User>> user = new User(); 

Od user.Post teraz będzie od rodzaju IPost<User> potencjalnie będą kodować takie linie:

IUser<IPost<User>> user = new User(); 
    user.Post = new PicturePost(); 

I będą doskonale kompilacji ale druga linia nie powiedzie się z błędem czasu wykonania! Dzieje się tak, ponieważ rzeczywisty typ user.Post to Post, a nie IPost lub PicturePost.

Tak więc, aby osiągnąć bezpieczeństwo typu, kompilator C# zabrania kompilacji, jeśli istnieje szansa, że ​​taki kod zostanie zapisany. W celu zapewnienia, że ​​nie będzie napisać taki kod, Post nieruchomość powinna być tylko do odczytu:

interface IUser<PostType> 
{ 
    PostType Post { get; } // No setter, this is readonly. 
} 

Teraz nie będzie w stanie napisać zły kod i wszystkie zwyczaje Post będzie typu bezpieczny w odniesieniu jego interfejsu, ponieważ można po prostu uzyskać go, a następnie idealnie przypisać do zmiennej jego interfejsu.

Ale to nie wystarczy, aby powiedzieć kompilatorowi, że twój interfejs po jasnej stronie, musisz wyraźnie określić, że twój parametr typu jest tylko poza (możesz go użyć, ale nie możesz go przekazać). Tak więc, mając poniżej realizacji interfejsu (zauważ out słowa kluczowego), Twój kod zostanie skompilowany:

interface IUser<out PostType> 
{ 
    PostType Post { get; } // No setter, this is readonly. 
} 

    // Both lines compile! 
    IUser<IPost<User>> user = new User(); 
    IUser<Post> user1 = new User(); 

nadzieja Ciągle jest prosta i nie przegap punkt w tym samym czasie :)