C# 泛型接口(interface)协变

标签 c# generics covariance

我不确定这里发生了什么,但我在使用以下代码时遇到了编译器错误:

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

如果 Post 为什么我会收到错误消息是 IPost<User> 的子类型?我知道在这种情况下我可以使用 User而不是 IUser<IPost<User>> ,但我想知道为什么这不起作用。

最佳答案

我会尝试用简单的例子来解释它。假设你还有一个实现 IPost<User> 的类:

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

那么这段代码将无法编译:

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

因为 user.Post属于具体类Post这与 PicturePost 不兼容(他们是 sibling )。

然后想象一下您问题中的那一行已成功编译:

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

user.Post现在将是 IPost<User> 类型你可能会编写这样的代码:

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

它们会完美编译,但第二行会因运行时错误而失败!这是因为 user.Post 的实际类型是Post不是IPostPicturePost .

所以,为了实现类型安全,C#编译器如果有可能写出这样的代码,就会禁止编译。为了保证你不会写出这样的代码,Post属性应该是只读的:

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

现在你将无法编写恶意代码,Post的所有用法就其接口(interface)而言将是类型安全的,因为您可以获取它,而不是完美地分配给其接口(interface)的变量。

但这还不够,要告诉编译器你的接口(interface)在light side,你需要显式指定你的类型参数只是out(你可以使用它,但你不能传递它进入)。因此,具有以下接口(interface)实现(注意 out 关键字),您的代码将编译:

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

希望我保持简单并且同时没有错过重点:)

关于C# 泛型接口(interface)协变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15596005/

相关文章:

c# - 访问 .net 核心中的 IServiceProvider/容器

c# - 如何使用 ASP.NET MVC 将数据从数据库导出到 Excel 工作表?

java - 使用 Guava 检查泛型类

c# - 方法的类型推断,具有泛型和类继承

Scala - 用边界覆盖类型成员

java - Java 通配符泛型 <?> 的 .NET 等效项具有协方差和逆方差?

c# - 如何检查 `IEnumerable<T1>` 是否与 `IEnumerable<T2>` 协变?

c# - 作为属性和索引器的数组

c# - 如何将查询字符串参数转换为asp.net mvc 4中的路由

c++ - `shared_ptr` 是如何实现协变的?