当前位置: 首页 > article >正文

C#中泛型的协变和逆变

协变:

在泛型接口中,使用out关键字可以声明协变。这意味着接口的泛型参数只能作为返回类型出现,而不能作为方法的参数类型。

示例:泛型接口中的协变

假设我们有一个基类Animal和一个派生类Dog

csharp复制

public class Animal { }
public class Dog : Animal { }

接下来,定义一个协变的泛型接口IEnumerable<out T>,其中out关键字表示泛型参数T是协变的:

csharp复制

public interface IEnumerable<out T>
{IEnumerator<T> GetEnumerator();
}

在实际使用中,可以将派生类型的集合赋值给基类型的集合:

csharp复制

IEnumerable<Dog> dogs = new List<Dog> { new Dog(), new Dog() };
IEnumerable<Animal> animals = dogs; // 协变使得这行代码合法

这里,IEnumerable<Dog>可以赋值给IEnumerable<Animal>,因为DogAnimal的派生类。

限制

协变是C#中一种强大的类型转换机制,它使得代码更加灵活,同时保持类型安全。

  • 协变只能应用于返回类型,不能应用于方法的参数类型。例如,以下代码是非法的:

  • csharp复制

    public interface IExample<out T>
    {void Set(T value); // 错误:协变类型不能作为方法的参数
    }

  • 2. 泛型委托中的协变

    在泛型委托中,同样可以使用out关键字来实现协变。协变允许将派生类型的委托赋值给基类型的委托。

    示例:泛型委托中的协变

    假设我们有以下基类和派生类:

  • csharp复制

    public class Animal { }
    public class Dog : Animal { }

    定义一个协变的泛型委托Func<out T>

    csharp复制

    public delegate T Func<out T>();

    在实际使用中,可以将派生类型的委托赋值给基类型的委托:

    csharp复制

    Func<Dog> getDog = () => new Dog();
    Func<Animal> getAnimal = getDog; // 协变使得这行代码合法

    这里,Func<Dog>可以赋值给Func<Animal>,因为DogAnimal的派生类。

    限制
  • 协变委托只能应用于返回类型,不能应用于委托的参数类型。例如,以下代码是非法的

  • csharp复制

    public delegate void Action<out T>(T value); // 错误:协变类型不能作为委托的参数

  • 3. 实际代码示例

    以下是一个完整的代码示例,展示如何在泛型接口和委托中使用协变:

  • using System;
    using System.Collections.Generic;// 定义基类和派生类
    public class Animal { }
    public class Dog : Animal { }// 泛型接口中的协变
    public interface IEnumerable<out T>
    {IEnumerator<T> GetEnumerator();
    }// 泛型委托中的协变
    public delegate T Func<out T>();class Program
    {static void Main(){// 泛型接口中的协变IEnumerable<Dog> dogs = new List<Dog> { new Dog(), new Dog() };IEnumerable<Animal> animals = dogs; // 协变使得这行代码合法Console.WriteLine("泛型接口中的协变成功!");// 泛型委托中的协变Func<Dog> getDog = () => new Dog();Func<Animal> getAnimal = getDog; // 协变使得这行代码合法Console.WriteLine("泛型委托中的协变成功!");}
    }

    4. 总结

  • 泛型接口中的协变:通过在接口中使用out关键字,可以将派生类型的实例赋值给基类型的变量。

  • 泛型委托中的协变:通过在委托中使用out关键字,可以将派生类型的委托赋值给基类型的委托。

  • 限制:协变只能应用于返回类型,不能应用于方法的参数类型或委托的参数类型。

C#中,本来派生类就可以直接给基类赋值,那为什么还要使用协变呢?优点在哪里?

确实,C#中派生类的实例可以直接赋值给基类的变量,这是面向对象编程中的多态性(Polymorphism)的基本特性。然而,协变(Covariance)的作用并不仅仅局限于将派生类对象赋值给基类变量,它更多地是为了在泛型编程中提供更灵活的类型转换能力,同时保持类型安全。

1. 协变的背景和动机

在C#中,泛型类型(如List<T>)和泛型接口(如IEnumerable<T>)在设计时,默认是不变的(Invariant)。这意味着即使DogAnimal的派生类,List<Dog>也不能直接赋值给List<Animal>IEnumerable<Dog>也不能直接赋值给IEnumerable<Animal>。例如:

csharp复制

List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs; // 错误:不能直接赋值
IEnumerable<Dog> dogEnumerable = dogs;
IEnumerable<Animal> animalEnumerable = dogEnumerable; // 同样错误

这种限制在某些场景下显得过于严格,尤其是在处理泛型集合或委托时。协变的引入正是为了解决这种类型转换的限制。

2. 协变的优点

(1)更灵活的类型转换

协变允许将派生类型的泛型集合或委托赋值给基类型的泛型集合或委托。这使得代码更加灵活,减少了不必要的类型转换和冗余代码。例如:

csharp复制

IEnumerable<Dog> dogs = new List<Dog> { new Dog(), new Dog() };
IEnumerable<Animal> animals = dogs; // 协变使得这行代码合法

如果没有协变,你需要手动将IEnumerable<Dog>转换为IEnumerable<Animal>,这不仅繁琐,还可能引入错误。

(2)保持类型安全

协变的使用是安全的,因为它只允许将派生类型的集合或委托赋值给基类型的集合或委托。你不能将基类型的集合赋值给派生类型的集合(这会破坏类型安全)。例如:

csharp复制

IEnumerable<Animal> animals = new List<Animal>();
IEnumerable<Dog> dogs = animals; // 错误:逆变或显式转换

这种限制确保了类型安全,避免了运行时错误。

(3)简化代码设计

协变使得泛型接口和委托的使用更加自然,减少了对泛型参数的限制。例如,在委托中使用协变可以简化方法的返回类型处理:

csharp复制

Func<Dog> getDog = () => new Dog();
Func<Animal> getAnimal = getDog; // 协变使得这行代码合法

如果没有协变,你需要为每种类型定义不同的委托,这会增加代码的复杂性。

(4)与.NET框架的兼容性

.NET框架中许多核心接口(如IEnumerable<T>IEnumerator<T>)都支持协变。这使得开发者可以更自然地使用这些接口,而无需担心类型转换问题。例如,IEnumerable<T>的协变特性使得LINQ操作更加灵活。


3. 协变与多态的区别

虽然派生类可以直接赋值给基类变量,但这种多态性仅适用于非泛型类型。对于泛型类型(如List<T>IEnumerable<T>),默认情况下是不变的。协变的引入正是为了解决这种泛型类型转换的限制。

例如:

  • 多态性Dog可以直接赋值给Animal,因为Dog继承自Animal

  • 协变IEnumerable<Dog>可以赋值给IEnumerable<Animal>,因为Dog继承自Animal,并且IEnumerable<T>支持协变。

如果没有协变,IEnumerable<Dog>IEnumerable<Animal>之间无法直接转换,这会限制泛型的灵活性。

4. 总结

协变的主要优点在于它为泛型编程提供了更灵活的类型转换能力,同时保持了类型安全。它使得泛型接口和委托的使用更加自然,减少了不必要的类型转换和冗余代码。虽然派生类可以直接赋值给基类变量,但这种多态性并不适用于泛型类型。协变的引入正是为了解决这种限制,使得泛型编程更加强大和灵活。

逆变:

逆变(Contravariance)在C#中主要用于泛型接口和委托,允许将基类类型的参数传递给期望派生类类型的方法或委托。这种特性在某些特定场景下非常有用,尤其是在需要提高代码复用性和灵活性时。以下是逆变在具体场景中的应用示例:


1. 泛型接口中的逆变

逆变可以用于泛型接口,允许将一个实现基类接口的对象赋值给派生类接口的变量。这在比较器接口(如IComparer<in T>)和动作接口(如IAction<in T>)中非常常见。

示例:比较器接口

假设有一个基类Animal和派生类Dog

csharp复制

public class Animal { }
public class Dog : Animal { }

定义一个支持逆变的泛型接口IComparer<in T>

csharp复制

public interface IComparer<in T>
{int Compare(T x, T y);
}

实现一个比较器,用于比较Animal对象:

csharp复制

public class AnimalComparer : IComparer<Animal>
{public int Compare(Animal x, Animal y){// 比较逻辑return x.ToString().CompareTo(y.ToString());}
}

由于IComparer<in T>支持逆变,可以将AnimalComparer赋值给IComparer<Dog>

csharp复制

IComparer<Dog> dogComparer = new AnimalComparer();

优点:通过逆变,可以复用AnimalComparer来比较Dog对象,而无需为每个派生类单独实现比较器。

2. 委托中的逆变

逆变也支持委托,允许将一个接受基类类型参数的方法赋值给期望派生类类型参数的委托。这在事件处理、回调函数等场景中非常有用。

示例:事件处理

假设有一个基类Animal和派生类Dog

csharp复制

public class Animal { }
public class Dog : Animal { }

定义一个支持逆变的委托Action<in T>

csharp复制

public delegate void Action<in T>(T item);

实现一个方法,用于处理Animal对象:

csharp复制

void HandleAnimal(Animal animal)
{Console.WriteLine("Handling an Animal");
}

由于Action<in T>支持逆变,可以将HandleAnimal方法赋值给Action<Dog>

csharp复制

Action<Dog> handleDog = HandleAnimal;
handleDog(new Dog()); // 输出:Handling an Animal

优点通过逆变,可以使用一个通用的HandleAnimal方法来处理Dog对象,而无需为每个派生类单独实现处理方法。

自己总结:

协变:即平常使用的派生类就可以赋值给基类,但是当你用了List或者其他泛型的时候,就没那么好赋值,需要各种类型显示转换,这个时候协变就显得特别好用。

逆变:当我们拥有一个通讯的基类,各种通讯均继承这个基类,批量处理派生类的时候,可以将基类运用逆变的方法,作为派生类的参数,使用统一模板。

还有更好的理解,欢迎评论~~~

相关文章:

C#中泛型的协变和逆变

协变&#xff1a; 在泛型接口中&#xff0c;使用out关键字可以声明协变。这意味着接口的泛型参数只能作为返回类型出现&#xff0c;而不能作为方法的参数类型。 示例&#xff1a;泛型接口中的协变 假设我们有一个基类Animal和一个派生类Dog&#xff1a; csharp复制 public…...

Select 下拉菜单选项分组

使用<select>元素创建下拉菜单&#xff0c;并使用 <optgroup> 元素对选项进行分组。<optgroup> 元素允许你将相关的 <option> 元素分组在一起&#xff0c;并为每个分组添加一个标签。 <form action"#" method"post"><la…...

文件上传漏洞详细利用流程

一、了解基本术语 1、后门 像房子一样&#xff0c;前门后门都可以进出房子&#xff0c;而较之前门&#xff0c;后门更具有隐蔽性。电脑技术中的后门是抽象概念&#xff0c;意指隐蔽性高或不常用的&#xff0c;区别于常规操作所使用的一种出入口。现金网络后门形形色色&#x…...

蓝桥与力扣刷题(蓝桥 旋转)

题目&#xff1a;图片旋转是对图片最简单的处理方式之一&#xff0c;在本题中&#xff0c;你需要对图片顺时针旋转 90 度。 我们用一个 nm的二维数组来表示一个图片&#xff0c;例如下面给出一个 34 的 图片的例子&#xff1a; 1 3 5 7 9 8 7 6 3 5 9 7 这个图片顺时针旋转…...

transformer架构解析{掩码,(自)注意力机制,多头(自)注意力机制}(含代码)-3

目录 前言 掩码张量 什么是掩码张量 掩码张量的作用 生成掩码张量实现 注意力机制 学习目标 注意力计算规则 注意力和自注意力 注意力机制 注意力机制计算规则的代码实现 多头注意力机制 学习目标 什么是多头注意力机制 多头注意力计算机制的作用 多头注意力机…...

使用DiskGenius工具来实现物理机多硬盘虚拟化迁移

使用DiskGenius工具来实现物理机多硬盘虚拟化迁移 概述准备工作注意事项实操过程记录1、Win7虚拟机&#xff0c;安装有两个硬盘&#xff08;硬盘0和硬盘1&#xff09;&#xff0c;各分了一个区&#xff0c;磁盘2是一块未使用的磁盘2、运行DiskGenius程序&#xff0c;记录现有各…...

iOS安全和逆向系列教程 第5篇 iOS基础开发知识速览 - 理解你要逆向的目标

iOS安全和逆向系列教程 第5篇 iOS基础开发知识速览 - 理解你要逆向的目标 正如上一篇文章结尾所预告的&#xff0c;在完成环境搭建后&#xff0c;我们需要了解iOS开发的基础知识。这不是要求你成为一名iOS开发者&#xff0c;而是为了让你在逆向分析过程中能够理解应用的代码结…...

计算机常用单词

文章目录 计算机单词1-100101-200201-300301-400401-500501-600601-700701-800801-900901-10001001-11001101-12001201-13001301-14001401-15001501-16001601-1695 计算机单词 参考 1-100 1. file [英faɪl 美faɪl] n. 文件&#xff1b;v. 保存文件 2. command [英kəˈmɑ…...

TS的接口 泛型 自定义类型 在接口中定义一个非必须的属性

TS的接口 泛型 自定义类型 接口 新建一个ts文件&#xff0c;在里面定义一个接口 export interface PersonInter{id:string,name:string,age:number }在vue文件中引入这个ts文件 <script lang"ts" setup name"Person">import {type PersonInter} …...

76.读取计时器运行时间 C#例子 WPF例子

TimerManager&#xff1a;一个增强的定时器类&#xff0c;带时间管理功能 在使用定时器时&#xff0c;我们常常需要知道定时器的运行状态&#xff0c;比如它已经运行了多久&#xff0c;或者还剩下多少时间。然而&#xff0c;.NET 的 System.Timers.Timer 类本身并没有直接提供…...

React封装通用Table组件,支持搜索(多条件)、筛选、自动序号、数据量统计等功能。未采用二次封装调整灵活,包含使用文档

封装通用组件 一、封装思想二、react代码三、css代码四、实现效果五、使用文档 BasicTableModal 表格模态框组件1.组件简介2.功能特点3.使用方法基础用法宽度控制示例带筛选功能搜索功能示例自定义单元格渲染 4.API 说明PropsColumn 配置项Filter 配置项 5.注意事项 一、封装思…...

【JavaEE】-- 多线程(初阶)4

文章目录 8.多线程案例8.1 单例模式8.1.1 饿汉模式8.1.2 懒汉模式 8.2 阻塞队列8.2.1 什么是阻塞队列8.2.2 生产者消费者模型8.2.3 标准库中的阻塞队列8.2.4 阻塞队列的应用场景8.2.4.1 消息队列 8.2.5 异步操作8.2.5 自定义实现阻塞队列8.2.6 阻塞队列--生产者消费者模型 8.3 …...

WP 高级摘要插件:助力 WordPress 文章摘要精准自定义显示

wordpress插件介绍 “WP高级摘要插件”功能丰富&#xff0c;它允许用户在WordPress后台自定义文章摘要。 可设置摘要长度&#xff0c;灵活调整展示字数&#xff1b;设定摘要最后的显示字符&#xff0c; 如常用的省略号等以提示内容未完整展示&#xff1b;指定允许在摘要中显示…...

论文阅读 EEG-Inception

EEG-Inception: A Novel Deep Convolutional Neural Network for Assistive ERP-Based Brain-Computer Interfaces EEG-Inception是第一个集成Inception模块进行ERP检测的模型&#xff0c;它有效地结合了轻型架构中的其他结构&#xff0c;提高了我们方法的性能。 本研究的主要目…...

FFmpeg入门:最简单的音频播放器

FFmpeg入门&#xff1a;最简单的音频播放器 欢迎大家来到FFmpeg入门的第二章&#xff0c;今天只做一个最简单的FFmpeg音频播放器&#xff1b;同样&#xff0c;话不多说&#xff0c;先上流程图 流程图 以上流程和视频播放器的解码过程基本上是一致的&#xff1b; 不同点在于 S…...

物联网感应层数据采集器实现协议转换 数据格式化

数据采集器的核心功能实现涉及多个技术层面的协同工作,以下是各模块的详细实现解析: 协议转换实现 协议解析引擎:采用插件式架构,例如: P r o t o c o l P a r...

基于Linux系统的物联网智能终端

背景 产品研发和项目研发有什么区别&#xff1f;一个令人发指的问题&#xff0c;刚开始工作时项目开发居多&#xff0c;认为项目开发和产品开发区别不大&#xff0c;待后来随着自身能力的提升&#xff0c;逐步感到要开发一个好产品还是比较难的&#xff0c;我认为项目开发的目的…...

8.1.STM32_OLED

4.STM32_OLED 跟着江协科大的视频&#xff0c;无法点亮OLED屏幕解决办法 每个人使用的0.96寸OLED屏幕信号不一样&#xff0c;存在很多兼容性问题 归根结底就是驱动的问题&#xff01; 本人的OLED是SSD1306,在淘宝店铺找了驱动文件后成功点亮&#xff0c;示例见文末 请针对自…...

Netty笔记9:粘包半包

Netty笔记1&#xff1a;线程模型 Netty笔记2&#xff1a;零拷贝 Netty笔记3&#xff1a;NIO编程 Netty笔记4&#xff1a;Epoll Netty笔记5&#xff1a;Netty开发实例 Netty笔记6&#xff1a;Netty组件 Netty笔记7&#xff1a;ChannelPromise通知处理 Netty笔记8&#xf…...

【算法方法总结·三】滑动窗口的一些技巧和注意事项

【算法方法总结三】滑动窗口的一些技巧和注意事项 【算法方法总结一】二分法的一些技巧和注意事项【算法方法总结二】双指针的一些技巧和注意事项【算法方法总结三】滑动窗口的一些技巧和注意事项 【滑动窗口】 数组的和 随着 右边指针 移动一定是 非递减 的&#xff0c;就是 …...

LabVIEW虚拟弗兰克赫兹实验仪

随着信息技术的飞速发展&#xff0c;虚拟仿真技术已经成为教学和研究中不可或缺的工具。开发了一种基于LabVIEW平台开发的虚拟弗兰克赫兹实验仪&#xff0c;该系统不仅能模拟实验操作&#xff0c;还能实时绘制数据图形&#xff0c;极大地丰富了物理实验的教学内容和方式。 ​ …...

spring boot + vue 搭建环境

参考文档&#xff1a;https://blog.csdn.net/weixin_44215249/article/details/117376417?fromshareblogdetail&sharetypeblogdetail&sharerId117376417&sharereferPC&sharesourceqxpapt&sharefromfrom_link. spring boot vue 搭建环境 一、浏览器二、jd…...

清华团队提出HistoCell,从组织学图像推断超分辨率细胞空间分布助力癌症研究|顶刊精析·25-03-02

小罗碎碎念 今天和大家分享一篇2025-02-21发表于nature communications的文章&#xff0c;内容涉及病理空转单细胞。 从组织学图像推断细胞空间分布对癌症研究意义重大&#xff0c;但现有方法存在标注工作量大、分辨率或特征挖掘不足等局限。研究旨在开发一种高效准确的方法。 …...

分布式锁—2.Redisson的可重入锁一

大纲 1.Redisson可重入锁RedissonLock概述 2.可重入锁源码之创建RedissonClient实例 3.可重入锁源码之lua脚本加锁逻辑 4.可重入锁源码之WatchDog维持加锁逻辑 5.可重入锁源码之可重入加锁逻辑 6.可重入锁源码之锁的互斥阻塞逻辑 7.可重入锁源码之释放锁逻辑 8.可重入锁…...

html+js 轮播图

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>轮播图示例</title><style>/* 基本样式…...

vue3:初学 vue-router 路由配置

承上一篇&#xff1a;nodejs&#xff1a;express js-mdict 作为后端&#xff0c;vue 3 vite 作为前端&#xff0c;在线查询英汉词典 安装 cnpm install vue-router -S 现在讲一讲 vue3&#xff1a;vue-router 路由配置 cd \js\mydict-web\src mkdir router cd router 我还…...

23种设计模式之《备忘录模式(Memento)》在c#中的应用及理解

程序设计中的主要设计模式通常分为三大类&#xff0c;共23种&#xff1a; 1. 创建型模式&#xff08;Creational Patterns&#xff09; 单例模式&#xff08;Singleton&#xff09;&#xff1a;确保一个类只有一个实例&#xff0c;并提供全局访问点。 工厂方法模式&#xff0…...

Python 爬取唐诗宋词三百首

你可以使用 requests 和 BeautifulSoup 来爬取《唐诗三百首》和《宋词三百首》的数据。以下是一个基本的 Python 爬虫示例&#xff0c;它从 中华诗词网 或类似的网站获取数据并保存为 JSON 文件。 import requests from bs4 import BeautifulSoup import json import time# 爬取…...

C语言408考研先行课第一课:数据类型

由于408要考数据结构……会有算法题…… 所以&#xff0c;需要C语言来进行一个预备…… 因为大一贪玩&#xff0c;C语言根本没学进去……谁能想到考研还用得到呢&#xff1f;【手动doge&#xff08;bushi&#xff09; 软件用的是Clion&#xff0c;可以自行搜索教程下载使用。…...

03 HarmonyOS Next仪表盘案例详解(二):进阶篇

温馨提示&#xff1a;本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦&#xff01; 文章目录 前言1. 响应式设计1.1 屏幕适配1.2 弹性布局 2. 数据展示与交互2.1 数据卡片渲染2.2 图表区域 3. 事件处理机制3.1 点击事件处理3.2 手势…...