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
。
快速的回答是,您可以通过将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());**
}
}
现在你不会得到编译错误。