c#泛型、接口和继承

本文关键字:继承 接口 泛型 | 更新日期: 2023-09-27 18:11:58

我有两个接口:

public interface IAmA
{
}
public interface IAmB<T> where T : IAmA
{
}

和两个实现这些接口的类,像这样:

public class ClassA : IAmA
{
}
public class ClassB : IAmB<ClassA>
{
}

尝试使用这些类时,如下所示:

public class Foo
{
    public void Bar()
    {
        var list = new List<IAmB<IAmA>>();
        list.Add(new ClassB());
    }
}

我得到这个编译错误:

cannot convert from 'ClassB' to 'IAmB<IAmA>'

我知道我可以让编译器高兴使用:

public class ClassB : IAmB<IAmA>
{
}

但是我需要能够在ClassB中成为IAmB<>的类型参数,实现IAmA

c#泛型、接口和继承

快速的回答是,您可以通过将IAmB<T>的类型参数声明为协变来实现您的要求,仅当该类型用作返回类型:

public interface IAmB<out T> where T : IAmA
{
    T SomeMethod(string someparam);
}

out T意味着您可以使用比约束中指定的更具体的类型。

您将不能使用T作为参数。以下代码无法编译:

public interface IAmB<out T> where T : IAmA
{
    void SomeMethod(T someparam);
}

从文档

可以使用协变类型参数作为属于接口的方法的返回值,或作为委托的返回类型。不能将协变类型参数用作接口方法的泛型类型约束。

这不是编译器的怪癖。假设您可以声明一个协变方法参数,您的列表将最终包含一些不能处理IAmB<IAmA>参数的对象—它们将期望输入ClassA或更具体。你的代码可以编译,但是在运行时失败。

这就引出了一个问题——为什么要使用IAmB<ClassA>呢?

在使用

之前,您应该考虑一下,因为可能有其他更合适的方法来解决您的实际问题。使用一个泛型接口实现一个具体类型,而试图像使用它实现另一个接口一样使用它,这是不寻常的。

你可以查看MSDN文档中关于协方差和逆变的部分,以及Eric Lippert和Jon Skeet对这个SO问题的回答:协方差和逆变的区别

快速回答:使泛型类型协变(见msdn)在您的接口

public interface IAmB<out T> where T : IAmA
{
}

这将解决编译问题。

但这并不能回答Panagiotis Kanavos的why问题!

技巧是使IAmB<T> 上的类型约束T协变,使用out关键字:

public interface IAmB<out T> where T : IAmA
{
}

这允许您使用比最初指定的更具体的类型,在本例中允许您将IAmB<ClassA>分配给IAmB<IAmA>类型的变量。

更多信息请参见文档

我只是告诉为什么这个错误报告。

如果你的IAmB有一个方法

public interface IAmB<T> where T : IAmA
{
    void foo(T p);
}
public class ClassB : IAmB<ClassA>
{
    void foo(ClassA p)
    {
        p.someIntField++;
    }
}

我们有另一个类

public class ClassC : IAmB<ClassA2>
{
    void foo(ClassA2 p)
    {
        p.someOtherIntField++;
    }
}

我们假设List<IAmB<IAmA>>.Add(T p)是这样实现的

IAmA mParam = xxxx;
void Add(IAmB<IAmA>> p){
    p.foo(mParam);
}

认为所有编译OK。你传递一个ClassB实例给List.Add,它变成

void Add(IAmB<IAmA>> p){
    //p is ClassB now
    p.foo(mParam);//COMPILER CAN NOT MAKE SURE mParam fit ClassB.foo
}

可以用逆变和协方差来求解。

public interface IAmA
{
}
**public interface IAmB<out T> where T : IAmA
{
}**

public class ClassA : IAmA
{
}
public class ClassB : IAmB<ClassA>
{
}

public class Foo
{
    public void Bar()
    {
        var list = new List<IAmB<IAmA>>();
        **list.Add(new ClassB());**
    }
}

现在你不会得到编译错误。