c#中带有方差的泛型方法

本文关键字:泛型方法 方差 | 更新日期: 2023-09-27 17:52:38

考虑以下类(继承树):

public class A {}
public class B: A {}

这个方法:

public IList<A> MyMethod(){
    IList<B> result = new List<B>();
    //add some items to result
    return result;
}

编译器不高兴。错误是Cannot convert expression type IList<B> to return type IList<A>。怎么解呢?换句话说,如何指定MyMethod将返回TIList<T>,其中T可以是继承自AA本身的任何实例?

c#中带有方差的泛型方法

您所要求的是不可能的,因为IList<T>不支持方差—您不能在期望IList<A>的任何地方使用IList<B>。为了想出一个解决方案,你必须更详细地解释你想要什么。

可能的解决方案如下:

public IList<A> MyMethod(){
    IList<A> result = new List<A>();
    //add some items to result
    return result;
}

public IEnumerable<A> MyMethod(){
    IList<B> result = new List<B>();
    //add some items to result
    return result;
}

不能转换IList到IList,即使B继承自A,否则,用户可能会尝试将A的非B实例添加到列表中。

public void Example(){
    IList<B> listB = new List<B>();
    IList<A> listA = listB;
    listA.Add(new A()); // Can't insert A into a list of B
}

能否返回IEnumerable而不是IList?IEnumerable是协变的,不像IList

如何指定MyMethod将返回T的IList,其中T可以是继承自A或A本身的实例的任何东西?


你不需要

你可以声明它返回IList<A>。为什么?因为——假定B继承自A——B的每一项都可以传递到所需类型为A的地方。

可以称其为继承多态性、Liskov替代原则或方法方差,名称无关紧要。重要的是,以下工作(在LinqPad上测试):

public class A {}
public class B: A {}
public IList<A> MyMethod()
{
    var result = new List<A>();
    //add some items to result
    result.Add(new B());
    return result;
}

遗传选择

事实上,您可以看出您将返回一个IList<TA>并请求一些派生类型(TB, TC…)来填充它。没错,下面的例子也可以工作(在LinqPad上测试):

void Main()
{
    MyMethod<A, B, C>();
}
public class A {}
public class B: A {}
public class C: A {}
public IList<TA> MyMethod<TA, TB, TC>()
    where TB : TA, new()
    where TC : TA, new()
    where TA : class
{
    var result = new List<TA>();
    //add some items to result
    result.Add(new B() as TA);
    result.Add(new C() as TA);
    return result;
}

或者如果你想保留一个特定的基类型(比如你想返回一个IList<A>,但它实际上包含了从A派生的类的项),那么你可以这样做:

void Main()
{
    MyMethod<B, C>();
}
public class A {}
public class B: A {}
public class C: A {}
public IList<A> MyMethod<T1, T2>()
    where T1 : A, new()
    where T2 : A, new()
{
    var result = new List<A>();
    //add some items to result
    result.Add(new T1() as A);
    result.Add(new T2() as A);
    return result;
}

你不需要,但是你可以

好的,如果你真的想说它返回IList<T> where T : A。那就说出来!

void Main()
{
    MyMethod<B>();
}
public class A {}
public class B: A {}
//public class C: A {} //Even if I don't add that class
public IList<T> MyMethod<T>()
    where T : A, new()
{
    var result = new List<T>();
    //add some items to result
    result.Add(new T());
    return result;
}

是的,它不能返回类型为T和类型为A的项目的混合,因为它说它返回IList<T>,而不是每个类型为A的项目也都是类型为T的项目。


你的代码发生了什么

查看你的代码:

public IList<A> MyMethod(){
    IList<B> result = new List<B>();
    //add some items to result
    return result;
}

当你说你要返回IList<A>时,你试图返回IList<B>。让我们假设这行得通……那么你的方法的调用者会发生什么?让我们看看:

public class A {}
public class B: A {}
public class C: A {}
void Main()
{
    //Hmmm... I need a IList<T>, let's call MyMethod!
    IList<A> list = MyMethod();
    //Cool, I got an IList<A>, now let's add some items...
    var item = new C();
    //Well, item is of type C...
    // and C inherits from A, so I must be able to add it...
    list.Add(item); //BOOM!
    //It was actually an IList<B>!
    // and C doesn't dervive from B, so you can't add it.
}

DFTBA !