一文带你了解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# 小数位数保留的方法集锦

    C# 小数位数保留的方法集锦,这是一个非常实用的技能点,下面我将为大家详细讲解。 1. 保留小数位数的方法 在C# 中,一些数值类型都有自己的 ToString() 方法,这个方法可以用来将值转换为字符串并指定小数位数。在下面的示例中,我们使用 Double 类型,并设置 ToString() 方法来指定小数位数。 double num = 3.141592…

    C# 2023年6月6日
    00
  • C#实战之备忘录的制作详解

    C#实战之备忘录的制作详解 简介 本文将介绍如何使用C#语言制作一个简单的备忘录,包括备忘录的基本功能、界面设计、代码实现等内容。 功能说明 本备忘录主要功能如下: 添加备忘录:用户可以通过界面向备忘录中添加新的备忘录。 查看备忘录:用户可以查看备忘录中已经添加的备忘录。 修改备忘录:用户可以修改备忘录中已经添加的备忘录。 删除备忘录:用户可以删除备忘录中已…

    C# 2023年6月1日
    00
  • C#页码导航显示及算法实现代码

    当网站中的内容较多时,常常需要用到分页功能来实现良好的用户体验。下面就是C#页码导航的实现。 一、分页算法 如何实现分页?我们需要首先确定一页要显示的记录数(pageSize)和当前页码(currentPageNum),其次需要得到数据表中数据总数(totalCount),然后根据这三个值来计算总的页数(totalPageNum)。 totalPageNum…

    C# 2023年6月7日
    00
  • c#接口使用示例分享

    下面是详细讲解“c#接口使用示例分享”的完整攻略,包含以下几个部分: 1. 接口的介绍 在面向对象编程中,接口是一种重要的概念。接口定义了一个类应该具备的方法或属性,但并不实现这些方法或属性的具体逻辑。相反,这些方法或属性的实现需要由实现了接口的类来完成。这使得接口能够在不知道具体实现的情况下对代码进行抽象和规范。在C#中,接口通常被定义为使用 interf…

    C# 2023年6月1日
    00
  • asp.net 2.0里也可以用JSON的使用方法

    ASP.NET 2.0是一个非常流行的.NET开发框架,它可以与JSON(JavaScript Object Notation)结合使用以进行数据交换。下面是使用JSON的方法的完整攻略: 1. 引入JavaScriptSerializer .NET Framework 2.0引入了JavaScriptSerializer类,可以使用它来序列化和反序列化.N…

    C# 2023年5月31日
    00
  • 浅谈C# 9.0 新特性之只读属性和记录

    当然,我很愿意为您讲解“浅谈C#9.0新特性之只读属性和记录”的完整攻略。下面是详细的解释。 什么是C# 9.0? C# 是一种由微软推出的面向对象编程语言,其 9.0 版本于 2020 年 11 月发布。C# 9.0 带来了许多新特性和语言改进,使得编写高效、可维护的代码更加容易。 只读属性 只读属性是指,一旦属性被初始化之后,就不能再次赋值。在 C# 9…

    C# 2023年5月15日
    00
  • C# System.TypeInitializationException 异常处理方案

    首先我们来简单地了解一下什么是”System.TypeInitializationException”异常。 “System.TypeInitializationException”是.NET框架中的一种异常,它通常发生在类或结构体初始化时,当初始化过程中发生错误时就会抛出该类异常。例如,在类的静态构造函数中,初始化对象时出现错误,或者在静态变量初始化期间出…

    C# 2023年5月15日
    00
  • 浅谈c#中const与readonly区别

    浅谈C#中const与readonly区别 在C#编程中,常量(constant)和只读字段(readonly field)是两种常见的实现常量的方式。但是这两种方式有着不同的使用场景和限制。本文将详细讲解C#中const和readonly的区别及其使用方法。 const常量 const关键字用于定义编译时常量,必须在定义时进行初始化,并且初始化的值不能被修…

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