2013-03-24 7 views
5

Ich bin mir nicht sicher, was hier vor sich geht, aber ich bin ein Compiler-Fehler immer mit dem folgenden Code:C# Generics Schnittstelle Kovarianzstrukturen

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(); 
    } 
} 

Warum erhalte ich einen Fehler, wenn Post ein Subtyp von IPost<User> ist? Ich weiß in diesem Fall könnte ich einfach User anstelle von IUser<IPost<User>> verwenden, aber ich möchte wissen, warum das nicht funktioniert.

+0

können Sie beginnen [hier ] (http://blogs.msdn.com/b/ericlippert/archive/2007/10/26/covariance-and-contravariance-in-c-part-five-interface-variance.aspx) –

Antwort

12

ich es mit einfachen Beispiel zu erklären versuchen. Angenommen, Sie eine weitere Klasse haben Implementierung IPost<User>:

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

Dann wird dieser Code nicht kompilieren:

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

Da user.Post Betonklasse ist Post die mit PicturePost nicht kompatibel ist (sie Geschwister sind).

Dann diese Zeile aus Ihrer Frage vorstellen, erfolgreich kompiliert wurde:

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

Seit user.Post jetzt wird der Typ sein IPost<User> Sie möglicherweise solche Zeilen Code:

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

Und sie werden kompilieren perfekt, aber die zweite Zeile wird mit Laufzeitfehler fehlschlagen! Dies liegt daran, dass der tatsächliche Typ von user.PostPost nicht IPost oder ist.

Also, um Typ-Sicherheit zu erreichen, verbietet der C# -Compiler das Kompilieren, wenn die Möglichkeit besteht, dass ein solcher Code geschrieben wird. Um sicherzustellen, dass Sie nicht einen solchen Code schreiben, sollten Post Eigenschaft schreibgeschützt werden:

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

Jetzt werden Sie nicht in der Lage sein, bösen Code zu schreiben, und alle Verwendungen von Post wird typsicher in Bezug sein von seiner Schnittstelle, da Sie nur erhalten es, und dann perfekt zuweisen Variable seiner Schnittstelle.

Aber das ist nicht genug, zu sagen, Compiler, der Ihre Schnittstelle auf der hellen Seite, müssen Sie explizit angeben, dass der Typ-Parameter ist nur aus (Sie es verwenden können, aber man kann es nicht in passieren). Also, unter Umsetzung der Schnittstelle mit (out Stichwort bemerken), der Code kompiliert:

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(); 

Hoffnung Ich hielt es einfach und ließ es sich nicht Punkt zur gleichen Zeit :)