This question already has answers here:
C# variance problem: Assigning List<Derived> as List<Base>
                                
                                    (5个答案)
                                
                        
                        
                            Covariance and IList
                                
                                    (4个答案)
                                
                        
                                去年关闭。
            
                    
假设有一个像Cup这样的基类:

public abstract class Cup { }


并假设存在PaperCup继承CupPlasticCup继承PaperCup类的情况。

public class PlasticCup : PaperCup { }
public class PaperCup : Cup { }


并假设Main类中有两种方法。

static void Test01 (PaperCup cup) { }
static void Test02 (Cup cup) { }




测试1

PaperCup A = new PaperCup();
Test01(A);
Test02(A);


上面的代码工作正常。可以将A实例传递到这两个函数中,因为它是PaperCup本身,并且是Cup基类的继承。

测试2

PlasticCup B = new PlasticCup();
Test01(B);
Test02(B);


上面的代码仍然可以正常工作。尽管B实例是PlasticCup,但也可以被函数使用,但它继承于PaperCup,并且最终从Cup派生。

但是泛型!

让我们看看以下方法。

static void Test010 (IList<PaperCup> cup) { }
static void Test011(IList<PlasticCup> cup) { }
static void Test012(IList<Cup> cup) { }


并且下面的该试验将在两个方法调用处失败。

IList<PlasticCup> BB = new List<PlasticCup>();
Test010(BB); // Fail CS1503 Compiler Error
Test011(BB);
Test012(BB); // Fail CS1503 Compiler Error




简单问题

为什么当采用Generic时无法将派生类型传递给这些函数?因为C#是OOP语言,它不应该工作吗?

最佳答案

好吧,想象一下允许以下情况:

IList<Cup> plasticCups = new List<PlasticCup>();


然后,可能会发生以下情况:

plasticCups.Add(new PaperCup()); //ouch!


您刚刚在塑料杯列表中插入了一个纸杯。那似乎不对。

您要问的是generic type varianceIList<T>T中是不变的,因为任何其他选项(协变或逆变)都不安全。

C#确实允许在接口和委托中使用泛型类型差异,但只有在编译器可以确保其安全性时才允许使用。

例如IEnumerable<out T>T中是协变的,因为您不能在IEnumerable<PlasticCup>中插入纸杯(规则是,实际上绝对不要将T类型用作方法参数),因此以下内容很完美安全,可以编译:

IEnumerable<Cup> plasticCups = Enumerable.Empty<PlasticCup>();


有趣的是,由于C#诞生时出于“支持其他现有语言功能”的原因,数组也支持类型差异,但它完全被破坏了。对于数组,这是完全合法的:

object[] strings = new string[] { .... }
strings[0] = new object(); //oh oh


并且您将得到一个不幸的运行时异常。使用泛型,这不可能发生。

09-26 08:16