详析C#的协变和逆变

详析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>,因为CatAnimal的子类,而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>存在继承关系,但这个关系在PenPencil之间已经明确存在了。从类型系统的角度看,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技术站

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

相关文章

  • 十分钟打造AutoComplete自动完成效果代码

    AutoComplete自动完成效果是一种常见的交互式UI组件,它可以帮助用户快速找到他们正在寻找的内容。本文将提供详解如何在十分钟内打造AutoComplete自动完成效果的完整攻略,包括使用jQuery UI的autocomplete方法、使用Bootstrap的typeahead插件等。同时,本文还提供两个示例,演示如何使用jQuery UI和Boot…

    C# 2023年5月15日
    00
  • WPF利用RPC调用其他进程的方法详解

    WPF利用RPC调用其他进程的方法详解 WPF(Windows Presentation Foundation)是一种用于构建Windows桌面应用程序的技术。在WPF应用程序中,我们可以使用RPC(Remote Procedure Call)调用其他进程的方法。本文将详细讲解如何在WPF应用程序中使用RPC调用其他进程的方法,并提供两个示例。 1. 创建R…

    C# 2023年5月15日
    00
  • C#访问C++动态分配的数组指针(实例讲解)

    初步分析这个问题,我们可以将其分为以下几个部分来进行回答: 什么是C++动态分配的数组指针? 为什么需要使用C#来访问C++动态分配的数组指针? 怎么使用C#来访问C++动态分配的数组指针? 示例说明。 下面逐一进行回答。 1. 什么是C++动态分配的数组指针? C++中的数组指针,是指指向数组的指针。动态分配的数组指针是指,程序在运行时根据需要动态分配内存…

    C# 2023年6月7日
    00
  • 一个.net 压缩位图至JPEG的实例代码

    针对.NET中压缩位图至JPEG的要求,我们可以通过以下步骤来实现: 1. 加载压缩前的位图 我们可以使用Bitmap类的FromStream方法,从文件流或内存流中获取位图,如下所示: using System.Drawing; //… //加载要压缩的位图 Bitmap bmpBefore = new Bitmap("D:/test.bmp…

    C# 2023年5月31日
    00
  • C#生成互不相同随机数的实现方法

    下面是“C#生成互不相同随机数的实现方法”的攻略。 1. 问题背景 在某些情况下,我们需要在程序中生成一组互不相同的随机数。例如,需要为多个用户分配不同的抽奖号码或者生成一组随机的测试数据。 2. 方案思路 实现这个需求的一种思路是:每次使用随机数时,从一个预设的随机数池中选取一个未使用过的数作为结果。这个思路的优点是可以确保生成的随机数互不相同,缺点则是需…

    C# 2023年6月7日
    00
  • .NET实现:将EXE设置开机自动启动

    首先需要说明的是,将EXE设置开机自动启动的操作不是由.NET实现的,而是由操作系统和桌面环境提供的功能实现的。 在Windows操作系统中,可以通过两种方式实现将EXE设置开机自动启动。 1.在启动文件夹中创建快捷方式 在Windows操作系统中,可以将应用程序的快捷方式放置到启动文件夹中,这样系统会在启动时自动运行该快捷方式所指向的应用程序。 要将应用程…

    C# 2023年5月15日
    00
  • C# Process.Start()方法: 启动一个新进程并打开一个可执行文件

    说明: C#中的Process.Start()方法可以用于启动一个外部程序或进程。该方法有很多重载版本,可以接受参数并且具有不同的用途,比如启动应用程序、打开文件、运行命令等等。下面将详细讲解其作用和使用方法,并提供至少两个实例。 一、Process.Start()方法的作用 Process.Start()方法是C#中启动外部程序的最简单也是最常见的方法。该…

    C# 2023年4月19日
    00
  • c#通用登录模块分享

    C#通用登录模块分享 在网站或应用程序中,用户通常需要进行登录才能使用其功能。为了节省开发人员的时间和精力,我们可以编写一个通用登录模块,以供多个网站或应用程序共用。本篇文章将详细介绍如何实现这个通用登录模块。 实现基础功能 创建用户表 首先,我们需要在数据库中创建一个用户表(如MySQL、SQL Server、Oracle等),用于保存用户的登录信息。用户…

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