在讲解.NET 4.0中的泛型协变和反变之前,需要先了解一下泛型的一些基本概念。
泛型的基本概念
在 C# 中,泛型是为了让我们在编写代码时更加灵活而设计的一个特性。泛型的核心是参数化类型,它可以让我们在编写代码时,不确定类型、保证类型安全、重用代码。通俗的说,泛型就是让代码能够适用于任何数据类型,如List
那么,什么是泛型协变和反变呢?
泛型协变和反变
泛型的协变和反变是在.NET Framework 4.0才开始支持的。简单来说,协变和反变是一种允许类型参数在继承关系中发生变化的特性。协变是指将派生类对象赋值给基类引用(C# 中用 out 修饰符实现),而反变则相反(C# 中用 in 修饰符实现)。
下面我们分别来看一下泛型协变和反变的示例说明。
泛型协变示例
我们假设有这么一个类:
class Animal
{
public virtual void Speak()
{
Console.WriteLine("I'm an animal.");
}
}
class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("I'm a cat.");
}
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("I'm a dog.");
}
}
接下来,我们定义一个接口:
interface IAnimalCollection<out T>
{
T GetAnimal();
}
这里我们使用了 out
关键字,表示 T
是协变的,也就是说,我们可以将泛型接口的派生类型的对象赋值给泛型接口的基类类型的引用。
然后我们再定义一个实现这个接口的类:
class AnimalCollection<T> : IAnimalCollection<T> where T : Animal
{
private readonly T[] _animals;
public AnimalCollection(params T[] animals)
{
_animals = animals;
}
public T GetAnimal()
{
return _animals.FirstOrDefault();
}
}
我们发现,这个类实现了泛型接口 IAnimalCollection<T>
,并且使用 where T : Animal
限制了泛型类型参数 T
,使其只能为 Animal
类型或其派生类型。
接下来,我们进行测试:
IAnimalCollection<Cat> catCollection = new AnimalCollection<Cat>(new Cat(), new Cat());
IAnimalCollection<Animal> animalCollection = catCollection;
这里,我们将 IAnimalCollection<Cat>
对象赋值给 IAnimalCollection<Animal>
类型的引用,这是合法的,这是因为 IAnimalCollection<T>
声明了 out
关键字,让其 “变化” 为协变类型,使得我们可以将基类类型的引用指向派生类类型的对象。
接下来,我们使用 animalCollection.GetAnimal()
方法获取动物对象:
Animal animal = animalCollection.GetAnimal();
animal.Speak(); // 输出 I'm a cat.
这里输出了 “I'm a cat.”,因为 catCollection
里有两只猫,而我们在执行 animalCollection.GetAnimal()
方法后,得到的第一个动物正是一只猫。泛型协变使得我们可以在运行时指定一种派生类类型,来获取其中的对象,从而实现更加灵活的编程。
泛型反变示例
我们看一个反变的示例。假设我们有这样一个类:
class SuperAnimal
{
public virtual void Speak()
{
Console.WriteLine("I'm a super animal.");
}
}
class BigCat : SuperAnimal
{
public override void Speak()
{
Console.WriteLine("I'm a big cat.");
}
}
class BigDog : SuperAnimal
{
public override void Speak()
{
Console.WriteLine("I'm a big dog.");
}
}
可以看到,这些动物都继承自 SuperAnimal
类。下面,我们创建一个接口:
interface ISuperAnimalCollection<in T>
{
void Add(T animal);
}
这里,我们使用了 in
关键字,表示 T
是反变的。也就是说,我们可以将泛型接口的基类类型的对象赋值给泛型接口的派生类型的引用。
然后我们再定义一个实现这个接口的类:
class SuperAnimalCollection<T> : ISuperAnimalCollection<T> where T : SuperAnimal
{
private readonly List<T> _animals = new List<T>();
public void Add(T animal)
{
_animals.Add(animal);
}
}
同样,这个类实现了泛型接口 ISuperAnimalCollection<T>
,并且使用 where T : SuperAnimal
限制了泛型类型参数 T
,使其只能为 SuperAnimal
类型或其派生类型。
接下来,我们测试一下:
ISuperAnimalCollection<SuperAnimal> superCollection = new SuperAnimalCollection<SuperAnimal>();
ISuperAnimalCollection<BigCat> bigCatCollection = superCollection;
bigCatCollection.Add(new BigCat());
这里,我们将 ISuperAnimalCollection<SuperAnimal>
对象赋值给 ISuperAnimalCollection<BigCat>
类型的引用,这是合法的,这是因为 ISuperAnimalCollection<T>
声明了 in
关键字,让其 “变化” 为反变类型,使得我们可以将派生类类型的引用指向基类类型的对象。
然后我们调用 bigCatCollection.Add(new BigCat())
方法添加一个 BigCat
对象,这也是合法的,因为 BigCat
是 SuperAnimal
的派生类。
通过这两个示例,我们可以看到,泛型协变和反变让我们能够更加灵活地编写代码,并且带来了更高的代码重用性。而在使用过程中,需要注意的是,协变和反变只能用于接口和委托的参数化类型,而不能用于泛型类。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解.NET 4.0中的泛型协变(covariant)和反变(contravariant) - Python技术站