详析C#的协变和逆变
在C#中,协变和逆变是非常重要的概念,尤其是在泛型的使用中更是如此。本文将详细讲解C#的协变和逆变。
协变
协变是一种安全的类型转换,从一个更特殊的类型转换为一个更一般的类型,也就是说,从子类型转换为父类型。在C#中,协变只支持泛型接口或泛型委托。使用out
关键字可以指示泛型类型参数是协变的。以下代码示例展示了协变的用法:
interface ICovariant<out T>
{
T Get();
}
class Animal { }
class Cat : Animal { }
class Program
{
static void Main(string[] args)
{
ICovariant<Cat> cat = new Covariant<Cat>();
ICovariant<Animal> animal = cat;
Console.WriteLine(animal.Get().GetType());
}
}
结果输出:
Covariance.Cat
从输出可知,通过ICovariant<Cat>
可以赋值给ICovariant<Animal>
,因为Cat
是Animal
的子类,而ICovariant
使用了out
关键字进行了协变,所以完全没问题。
再看一个例子:
class Pencil
{
public void Draw() { Console.WriteLine("Draw something with a pencil."); }
}
class Pen : Pencil
{
public void Write() { Console.WriteLine("Write something with a pen."); }
}
class MyQueue<T>
{
public void Enqueue(T item) { throw new NotImplementedException(); }
}
class Program
{
static void Main(string[] args)
{
MyQueue<Pen> penQueue = new MyQueue<Pen>();
MyQueue<Pencil> pencilQueue = penQueue;
pencilQueue.Enqueue(new Pencil()); // 编译错误!
}
}
从输出可以看到,penQueue
可以赋值给pencilQueue
,但是最后一个操作给pencilQueue
添加了一个Pencil
类型的元素,编译器会提示一个错误:无法将类型Pencil
隐式转换为类型Pen
。那么为什么会有这个错误呢?因为我们虽然在代码中没有明确的声明MyQueue<Pen>
与MyQueue<Pencil>
存在继承关系,但这个关系在Pen
与Pencil
之间已经明确存在了。从类型系统的角度看,MyQueue<Pen>
实际上是MyQueue<Pen>
类型中所有操作的集合,也就是说,pencilQueue.Enqueue(new Pencil())
其实就是在penQueue.Enqueue(new Pencil())
的语义上添加了一个坑,这是不允许的。
逆变
逆变是协变的对称概念,它表示在泛型类型参数中,既能够接受更通用的类型,也能够接受更特殊的类型。在C#中,逆变使用in
关键字进行标识。例如:
interface IContravariant<in T>
{
void Set(T item);
}
class Animal { }
class Cat : Animal { }
class Program
{
static void Main(string[] args)
{
IContravariant<Animal> animal = new Contravariant<Animal>();
IContravariant<Cat> cat = animal;
cat.Set(new Cat());
}
}
从代码中我们可以看到,从IContravariant<Animal>
类型的变量,可以赋值给IContravariant<Cat>
类型的变量,这是因为IContravariant
使用了in
关键字,表明此处参数是逆变的。因为Set
操作要求T
类型的参数是具有一定特殊性质的,所以可以逆变。逆变除了适用于泛型接口、泛型委托等也适用于委托,例如:
delegate void MyDelegate<in T>(T item);
class Animal { }
class Cat : Animal { }
class Program
{
static void Main(string[] args)
{
MyDelegate<Animal> animalDelegate = (Animal a) => { Console.WriteLine("This is an animal."); };
MyDelegate<Cat> catDelegate = animalDelegate;
catDelegate(new Cat());
}
}
从代码中我们可以看到,将参数类型更为范化的委托类型赋值给更为具体的,参数类型更为特化的委托类型可以是可以的。这和之前提到的委托的公共语言运行时有关,也就是说,在这个特定的语言运行时中,每个委托实例都能够将任意数量、类型和顺序的参数传递给调用方,而这两个委托是等价的。
总结来说,协变和逆变是C#语言中一个重要的泛型特性,可以让我们避免大量的重复代码。协变可以保证将一个更为特化的类型转换为更为广泛的类型是能够安全进行的,而逆变则可以保证将一个更为广泛的类型转换为更为特化的类型也是能够安全进行的。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详析C#的协变和逆变 - Python技术站