详解.NET 4.0中的泛型协变(covariant)和反变(contravariant)

在讲解.NET 4.0中的泛型协变和反变之前,需要先了解一下泛型的一些基本概念。

泛型的基本概念

C# 中,泛型是为了让我们在编写代码时更加灵活而设计的一个特性。泛型的核心是参数化类型,它可以让我们在编写代码时,不确定类型、保证类型安全、重用代码。通俗的说,泛型就是让代码能够适用于任何数据类型,如List、Dictionary等。

那么,什么是泛型协变和反变呢?

泛型协变和反变

泛型的协变和反变是在.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 对象,这也是合法的,因为 BigCatSuperAnimal 的派生类。

通过这两个示例,我们可以看到,泛型协变和反变让我们能够更加灵活地编写代码,并且带来了更高的代码重用性。而在使用过程中,需要注意的是,协变和反变只能用于接口和委托的参数化类型,而不能用于泛型类。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解.NET 4.0中的泛型协变(covariant)和反变(contravariant) - Python技术站

(0)
上一篇 2023年6月3日
下一篇 2023年6月3日

相关文章

  • C#创建及访问网络硬盘的实现

    C#创建及访问网络硬盘的实现 什么是网络硬盘 网络硬盘是一种将物理硬盘或云存储服务通过网络连接的形式,使得用户可以使用网络来进行硬盘存储和获取数据的设备或服务。 实现 在C#中,可以通过调用System.IO命名空间下的Directory类和File类等来创建及访问网络硬盘。 创建文件夹 对于创建文件夹,可以通过Directory.CreateDirecto…

    C# 2023年6月1日
    00
  • ng-repeat中Checkbox默认选中的方法教程

    以下是”ng-repeat中Checkbox默认选中的方法教程”的完整攻略: 1. 在ng-repeat中使用Checkbox 在ng-repeat中使用Checkbox很常见,当我们需要对列表项进行多选操作的时候就会用到Checkbox,如下所示: <ul> <li ng-repeat="item in items"&…

    C# 2023年5月31日
    00
  • C#静态static的用法实例分析

    C#静态static的用法实例分析 什么是C#静态static C#中可以使用static关键字来表示静态成员。所谓静态成员,就是可以在不实例化类的情况下访问的成员。静态成员可以是属性、方法、字段等。 public class MyClass { public static int MyStaticProperty { get; set; } public …

    C# 2023年5月31日
    00
  • C#中Byte[]和String之间转换的方法

    当需要处理二进制数据时,我们通常会用到Byte[]类型,而处理文本时则使用String类型。在C#中,Byte[]和String之间的相互转换可以通过以下方法进行。 Byte[] 转 String 1. 直接将 Byte[] 转为 String 可以使用Encoding类提供的GetString方法将Byte[]直接转为String。 byte[] byte…

    C# 2023年6月1日
    00
  • 武装你的WEBAPI-OData与DTO

    本文属于OData系列文章 Intro 前面写了很多有关OData使用的文章,很多读者会有疑问,直接将实体对象暴露给最终用户会不会有风险?$expand在默认配置的情况下,数据会不会有泄露风险? 答案是肯定的,由于OData的特性,提供给我们便捷同时也会带来一些风险。很多地方推荐使用DTO模式来隔离实体类与最终用户使用到类的关系,从而解决以上两个问题,ODa…

    C# 2023年5月8日
    00
  • C#中把Json数据转为DataTable

    让我们来介绍如何在C#中将Json数据转换为DataTable。在此之前需要先引入Newtonsoft.Json这个第三方库,可以通过NuGet安装。以下是详细步骤: 1. 读取Json数据 首先我们需要读取Json数据。可以从文件或Web API获取Json数据。以下是从文件读取Json数据的示例: using System.IO; string path…

    C# 2023年5月31日
    00
  • C#实现UI控件输出日志的方法详解

    标题:C#实现UI控件输出日志的方法详解 正文: 在C#中,我们通常使用控制台输出日志信息。但是,在UI应用程序中,我们更经常使用UI控件来展示日志信息。本文将详细介绍如何在C#中实现UI控件输出日志的方法。 基本思路 UI控件输出日志的基本思路是通过控制UI控件的Text属性,将日志信息添加到UI控件上,从而实现日志的输出。这个过程可以使用delegate…

    C# 2023年5月15日
    00
  • C#中类的使用教程详解

    C#中类的使用教程详解 什么是类 在C#中,类是一种自定义类型,它允许我们定义自己的数据类型以及与它相关的方法和事件。类包含了多个成员,包括属性、方法、字段、构造函数和事件等。使用类,我们可以把数据和相应的方法封装在一起,便于代码的管理和维护。 声明和定义类 定义一个类的语法格式如下: [修饰符] class 类名 { //类成员 } 其中,修饰符是可选部分…

    C# 2023年6月1日
    00
合作推广
合作推广
分享本页
返回顶部