一文带你了解C#中的协变与逆变

一文带你了解C#中的协变与逆变

什么是协变与逆变

在程序设计中,经常需要对类进行继承和实现接口的操作。在这样的过程中,我们通常会遇到这样的问题:子类或者实现接口的类的泛型参数类型和父类的泛型参数类型不匹配。而“协变”和“逆变”就是解决这样的问题的方法。

协变和逆变是 C# 4.0 引入的两个关键技术,可以让我们更加灵活地使用泛型。在 C# 中,协变和逆变可以用于委托、接口和数组等这些数据结构。

协变

协变是指在保持类型安全的前提下,可以将从泛型类派生的子类对象作为基类对象使用。也就是说,对于一个泛型类型 T, 如果 T1 是另一个类型 T2 的子类,那么 T 就是 T 的子类。

我们可以理解一个泛型类的协变是这个泛型类泛型参数的类型可以隐式向上转换。例如:

public interface IAnimal { }

public class Animal { }

public class Dog : Animal, IAnimal { }

public interface ITest<out T> where T : IAnimal
{
    T Get();
}

public class Test<T> : ITest<T> where T : IAnimal
{
    public T Get()
    {
        return default(T);
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        ITest<Animal> animalTest = new Test<Dog>(); //协变
    }
}

在上面的代码中,我们定义了一个 Test 类,实现了 ITest 接口,并且使用了 out 关键字限定了 T 的类型。其中,IAnimal 是 Animal 的子类,也就是说,Dog 类型是 Animal 的子类。

在 Main() 方法中,我们使用 Test() 对象来初始化 ITest 类型的 animalTest 对象,也就是说,我们成功地将一个泛型类型参数隐式转换为了一个更泛化的类型参数,这里就是把 Dog 类型转换为了 Animal 类型。

逆变

逆变与协变正好相反,是指在保持类型安全的前提下,可以将能够接受泛型类对象的类型更改为可以接受泛型类的基类。也就是说,对于一个泛型类型 T,如果 T1 是另一个类型 T2 的基类,那么 T 就是 T 的基类。

我们可以理解一个泛型类的逆变是这个泛型类泛型参数的类型可以隐式向下转换。例如:

public interface IAnimal { }

public class Animal { }

public class Dog : Animal, IAnimal { }

public interface ITest<in T> where T : Animal
{
    void Post(T animal);
}

public class Test<T> : ITest<T> where T : Animal
{
    public void Post(T animal)
    {
        //DO SOMETHING
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        ITest<Dog> dogTest = new Test<Animal>(); //逆变
    }
}

在上面的代码中,我们定义了一个 Test 类,实现了 ITest 接口,并且使用了 in 关键字限定了 T 的类型。其中,IAnimal 是 Animal 的子类,也就是说,Dog 类型是 Animal 的子类。

在 Main() 方法中,我们使用 Test() 对象来初始化 ITest 类型的 dogTest 对象,也就是说,我们成功地将一个泛型类型参数隐式转换为了一个更具体的类型参数,这里就是把 Animal 类型转换为了 Dog 类型。

示例

协变示例:IEnumerable

IEnumerable 接口是 C# 中的一个经典协变示例。这个接口定义了一个能够枚举 T 类型元素的集合。如果这个集合类型是一个枚举器 TItem,那么,IEnumerable 就是 IEnumerable 的子类型。

下面是一个利用协变的例子:

public static void Main(string[] args)
{
    var dogs = new List<Dog>
    {
        new Dog(),
        new Dog()
    };

    IEnumerable<IAnimal> animals = dogs;
}

在这个例子中,我们把 List 类型赋值给了 IEnumerable 类型,因为 Dog 类型是 IAnimal 类型的子类型。

逆变示例:Func

Func 委托是 C# 中的一个经典逆变示例。这个委托返回值的类型是 TResult,输入参数的类型是 TArg。如果 TResult 是 TResult1 的子类型,而 TArg1 是 TArg 的父类型,那么 Func 就是 Func 的基类型。

下面是一个利用逆变的例子:

public static void Main(string[] args)
{
    Func<Animal, string> animalToString = animal => animal.ToString();

    Func<Dog, string> dogToString = animalToString;

    Console.WriteLine(dogToString(new Dog()));
}

在这个例子中,我们把一个 Func 委托类型隐式地转换为了 Func 委托类型,即把一个 Animal 类型转换为了 Dog 类型。这实现了逆变。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:一文带你了解C#中的协变与逆变 - Python技术站

(0)
上一篇 2023年5月15日
下一篇 2023年5月15日

相关文章

  • C#中Params的用法

    下面是关于C#中Params用法的完整攻略。 什么是Params Params是C#中的一种关键字,在方法参数中使用,表示该参数可以接受零个或多个值。Params参数必须是方法中的最后一个参数,而且必须是数组类型。 Params的语法 Params的语法形式如下: public void Method(params string[] values) { //…

    C# 2023年6月7日
    00
  • C#中事件的定义和使用

    C#中的事件是一种特殊的委托类型,它可以使对象在某个特定的时间点上引发或触发某个动作或事件。事件可以用于编写响应用户交互、处理消息通知等许多用途。 一、事件的定义 事件定义的基本语法格式如下: public delegate void SomeEventHandler(object sender, EventArgs e); public class Som…

    C# 2023年5月31日
    00
  • C#列出当前系统所有正在运行程序的方法

    关于“C#列出当前系统所有正在运行程序的方法”的完整攻略,可以通过以下步骤来实现: 首先,需要引用System.Diagnostics命名空间,该命名空间提供了一些类,可以操作正在运行的进程,包括获取正在运行的进程信息。 接着,可以通过调用Process.GetProcesses()静态方法,来获取当前系统中正在运行的所有进程。该方法返回一个Process类…

    C# 2023年6月7日
    00
  • asp.net 执行事务代码

    下面是 “ASP.NET 执行事务代码” 的完整攻略: 什么是事务 事务是一组可被视为单个逻辑单元的操作,其中所有操作必须成功才能提交,否则必须回滚。这意味着要么所有的 SQL 语句都被执行且提交,要么执行如果任何一个 SQL 语句出现错误则所有过程不执行,回滚到最初状态。 在 ASP.NET 中,执行事务代码指的是在使用数据库时,通过对 SQL 语句的执行…

    C# 2023年5月31日
    00
  • swagger配置正式环境中不可访问的问题

    当我们在开发阶段使用Swagger来管理我们的REST API时,它对于我们进行API测试、API文档编写非常友好。但是在发布到正式环境时,我们需要注意以下几点,避免Swagger配置的API在正式环境中被未经授权的用户访问。 1. 在生产环境中禁用Swagger UI Swagger UI是Swagger的一个核心组件,它用于在浏览器中呈现API文档,并提…

    C# 2023年5月15日
    00
  • c# Graphics使用方法(画圆写字代码)

    C# Graphics使用方法(画圆写字代码) 在C#中,我们可以使用System.Drawing命名空间下的Graphics类来实现绘图功能,包括画笔、画刷、线条、形状等等。本篇攻略主要介绍如何使用Graphics类绘制圆和写字的相关代码。 创建 Graphics 对象 要使用Graphics类进行绘图,首先要创建Graphics对象。我们可以通过两种方式…

    C# 2023年6月7日
    00
  • WPF的数据绑定详细介绍

    WPF的数据绑定是WPF框架中的一个重要功能,它能够帮助我们将数据与界面进行绑定,使数据的变化自动反映在界面上。下面,我会给出关于数据绑定的详细介绍,包括数据绑定的意义、数据绑定的实现方式、数据绑定的语法等。同时,还会通过两个示例来说明数据绑定的应用。 1. 数据绑定的意义 在传统的Windows应用中,数据更新通常都是手动进行的,这样就需要在代码中写入大量…

    C# 2023年5月31日
    00
  • WPF+ASP.NET SignalR实现动态折线图的绘制

    下面是详细的攻略: 简介 本文介绍如何使用 WPF 和 ASP.NET SignalR 实现动态折线图的绘制。WPF 是一个用于创建 Windows 应用程序的 UI 框架,而 ASP.NET SignalR 是一个用于实现实时应用程序的框架,两者结合可以实现实时折线图的绘制。 准备工作 在开始实现动态折线图之前,我们需要准备以下工具: Visual Stu…

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