图解C#高级教程(四):协变、逆变
本章的主题是可变性(variance),这里的可变性更多的是指基类和派生类之间的转换。可变性分为三种:协变(covariance)、逆变(contravariance)和不变(invariance)。
文章目录
- 1. 协变
- 1.1 协变的概念
- 1.2 语法
- 1.3 使用场景
- 1.4 代码例子:使用委托实现协变
- 1.5 代码例子:LINQ 中使用协变
- 2. 逆变
- 2.1 逆变的概念
- 2.2 语法
- 2.3 使用场景
- 2.4 代码示例:委托中使用逆变
- 2.5 代码示例:事件处理中使用逆变
- 3. 一些问题
- 3.1 协变和多态的区别
- 概念区别
- 主要区别总结
1. 协变
在C#中,协变(Covariance)是一种允许将派生类类型替换为基类类型的特性。这种特性通常用于泛型类型或委托中,特别是在返回类型时。协变的主要目的是提高代码的灵活性和可重用性,使得在使用派生类时能够有效地利用基类的接口或方法。
1.1 协变的概念
协变是指在泛型类型的使用中,允许将某个类型参数替换为该参数的派生类。换句话说,协变允许你在泛型委托或接口中使用更具体的类型。在C#中,协变通常用于返回值的情况。
1.2 语法
在C#中,可以通过使用out关键字来声明协变类型参数。下面是协变的基本语法:
public delegate TResult MyDelegate<out TResult>();
这里,TResult 参数前面加了out关键字,表明这个类型参数是协变的。返回类型可以是派生类。
1.3 使用场景
协变的常见使用场景包括:
- 委托:在使用委托时,协变允许将一个返回派生类的委托赋值给返回基类的委托。
- LINQ:在LINQ查询中,使用协变来处理不同类型的集合。
- 事件处理:在事件处理程序中,使用协变来处理不同类型的事件。
1.4 代码例子:使用委托实现协变
using System;// 基类
public class Animal
{public virtual void Speak(){Console.WriteLine("Animal speaks");}
}// 派生类
public class Dog : Animal
{public override void Speak(){Console.WriteLine("Dog barks");}
}// 定义一个协变的委托
public delegate T AnimalDelegate<out T>();class Program
{static void Main(){// 将返回Dog类型的委托赋值给返回Animal类型的委托AnimalDelegate<Animal> animalDelegate = GetDog;// 调用委托并输出结果Animal animal = animalDelegate();animal.Speak(); // 输出: Dog barks}static Dog GetDog(){return new Dog();}
}
输出:
Dog barks
1.5 代码例子:LINQ 中使用协变
using System;
using System.Collections.Generic;
using System.Linq;public class Animal
{public string Name { get; set; }
}public class Dog : Animal { }class Program
{static void Main(){List<Dog> dogs = new List<Dog>{new Dog { Name = "Buddy" },new Dog { Name = "Max" }};// 使用LINQ进行查询,并返回基类类型的集合IEnumerable<Animal> animals = dogs.Select(d => d);foreach (var animal in animals){Console.WriteLine(animal.Name); // 输出: Buddy, Max}}
}
使用协变时,应该注意以下几点:
- 只能在返回值中使用协变,而不能在方法参数中使用。
- 协变使得代码更加灵活,但也可能引入类型安全问题,因此在使用时应谨慎。
2. 逆变
在C#中,逆变(Contravariance)是与协变相反的特性,允许将基类类型替换为派生类类型。逆变主要用于参数类型的上下文,特别是在方法参数时。通过逆变,我们可以使用更通用的类型来替代特定的类型,从而提高代码的灵活性和可重用性。
2.1 逆变的概念
逆变是指在泛型类型的使用中,允许将某个类型参数替换为该参数的基类。这种特性通常在需要处理不同类型的对象时非常有用。表现在代码上就是某个函数的参数类型是基类类型,但是可以接受其派生类类型的实参。
2.2 语法
在C#中,可以通过使用 in 关键字来声明逆变类型参数。下面是逆变的基本语法:
public delegate void MyDelegate<in T>();
2.3 使用场景
逆变的常见使用场景包括:
- 委托:在使用委托时,逆变允许将一个接受派生类的委托赋值给接受基类的委托。
- 事件处理:在事件处理程序中,使用逆变来处理不同类型的事件。
- 集合操作:在处理集合时,逆变可以帮助简化参数类型的定义。
2.4 代码示例:委托中使用逆变
下面的程序实现了一个基类类型的委托指向参数类型为基类类型的方法,但是在执行委托时,传入给委托的参数类型为派生类类型。
using System;// 基类
public class Animal
{public string Name { get; set; }
}// 派生类
public class Dog : Animal { }// 定义一个逆变的委托
public delegate void AnimalAction<in T>(T animal);class Program
{static void Main(){// 将接受Animal类型的委托赋值给接受Dog类型的委托AnimalAction<Animal> animalAction = MakeSound;Dog dog = new Dog { Name = "Buddy" };animalAction(dog); // 输出: Buddy makes a sound}static void MakeSound(Animal animal){Console.WriteLine($"{animal.Name} makes a sound");}
}
2.5 代码示例:事件处理中使用逆变
下面的代码中实现了使用事件和逆变,统一处理不同用户的目的。
public void AddUser(User user)
输出:
John has been added.
Admin has been added.
使用 in 和 out 关键字只适用于委托和接口,不适用于类、结构和方法。
不包括 in 和 out 关键字的委托和接口类型参数叫做不变。这些类型参数不能用于协变和逆变。
3. 一些问题
3.1 协变和多态的区别
概念区别
- 多态:多态是指子类可以替代父类的实例,调用相同的方法但可能会有不同的实现。在面向对象编程中,常通过继承和接口来实现多态性。
public class Animal
{public virtual void Speak(){Console.WriteLine("Animal speaks");}
}public class Dog : Animal
{public override void Speak(){Console.WriteLine("Woof!");}
}Animal myDog = new Dog();
myDog.Speak(); // 输出: Woof!
- 协变:协变是指在泛型类型参数中允许用派生类替代基类。在 C# 中,协变通常与泛型委托和接口相关,允许使用更具体的类型作为返回值。
public delegate T CovariantDelegate<out T>();public class Animal { }
public class Dog : Animal { }public static Dog GetDog() => new Dog();CovariantDelegate<Animal> animalDelegate = GetDog;
Animal animal = animalDelegate(); // 使用协变
主要区别总结
概念范围:
- 多态是一个更广泛的概念,涵盖了通过接口和继承实现不同类型之间的行为相同。
- 协变是关于泛型类型参数的特定实现,主要用于返回值的场景。
实现方式:
- 多态通常通过方法重写(override)和接口实现来实现,允许子类定义父类方法的具体实现。
- 协变是通过在泛型定义中使用 out 关键字来实现,允许使用更具体的类型作为返回值。
适用场景:
- 多态主要用于运行时行为的动态选择,允许对象通过父类接口调用不同的实现。
- 协变主要用于数据结构和类型安全的情况下,特别是在返回类型的灵活性方面。
各位道友,码字不易,记得一键三连呐。
相关文章:

图解C#高级教程(四):协变、逆变
本章的主题是可变性(variance),这里的可变性更多的是指基类和派生类之间的转换。可变性分为三种:协变(covariance)、逆变(contravariance)和不变(invariance)…...

详解CSS中的伪元素
4.3 伪元素 可以把样式应用到文档树中根本不存在的元素上。 ::first-line 文本中的第一行 ::first-letter 文本中的第一个字母 ::after 元素之后添加 ::before 元素之前 代码: <!DOCTYPE html> <html> <head><meta charset"utf-8&q…...

paper_template
paper_template Title 文章标题 Abstract 摘要 Keywords 关键词 Highlights Highlights / 创新点 Summary 写完笔记之后最后填,概述文章的内容,以后查阅笔记的时候先看这一段。 Backgrounds 描述当前研究背景 Research Objective 作者的研…...

【Bug】解决 Ubuntu 中 “error: Unable to Find Python3 Executable” 错误
解决 Ubuntu 中 “Unable to Find Python3 Executable” 错误 在 Ubuntu 系统上使用 Python 进行开发时,遇到找不到 python3 可执行文件的错误。 主要问题是无法正常打开终端(原生与terminator),找不到python3,且无法…...

CUDA与TensorRT学习六:模型部署-CNN、模型部署-YOLOv8检测器、部署BEVFusion模型
文章目录 一、模型部署-CNN二、模型部署-YOLOv8检测器三、部署BEVFusion模型 一、模型部署-CNN 二、模型部署-YOLOv8检测器 三、部署BEVFusion模型...

防sql注入的网站登录系统设计与实现
课程名称 网络安全 大作业名称 防sql注入的网站登录系统设计与实现 姓名 学号 班级 大 作 业 要 求 结合mysql数据库设计一个web登录页面密码需密文存放(可以采用hash方式,建议用sha1或md5加盐)采用服务器端的验证码&#…...

如何快速切换电脑的ip地址
在当今的数字化时代,IP地址作为网络身份的重要标识,其重要性日益凸显。无论是出于保护个人隐私的需要,还是为了访问特定的网络服务等,快速切换电脑的IP地址已成为许多用户的迫切需求。本文将为你介绍几种实用的方法,帮…...

鸿蒙HarmonyOS之选择相册文件(照片/视频)方法
一、新建文件工具类FileUtil.ets 包含:选择照片方法、获取文件类型方法、去除后缀、获取后缀方法 import { BusinessError, request } from kit.BasicServicesKit; import photoAccessHelper from ohos.file.photoAccessHelper; import bundleManager from ohos.b…...

【QT Qucik】C++交互:接收QML信号
在本节课中,我们将深入探讨如何在C中接收QML发出的信号。我们将分为几个部分,详细说明信号的定义、发送及其在C中的接收。 理解信号和槽机制 Qt的信号与槽机制是一种用于对象之间通信的强大工具。信号是对象在特定事件发生时发送的通知,而槽…...

【C++】关键字+命名空间
大家好,我是苏貝,本篇博客带大家了解C的命名空间,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️ 目录 一. 关键字二. 命名空间2.1 命名空间的定义2.2 命名空间的使用a. 命名空间名称作用域限定…...

网络层——IP
IP地址 结构: 由32位二进制数组成,通常用点分的形式被分为四个部分,每个部分1byte,最大值为255。 从功能的角度看,ip地址由两部分组成,网络号和主机号。网络号标识了ip所在的网段,主机号标识了…...

随笔 漫游互联网
网络编程基础:漫游互联网 温故而知新,可以为师矣。互联网我们可以想象成一个立体的网状结构,由一个一个的小网络组成的网状结构,在一个一个小网络中通过一台一台机器组成,经过几十年的发展终于有了今天这个样子。谈论…...

8.9K Star,开源自托管离线翻译引擎
Hi,骚年,我是大 G,公众号「GitHub 指北」会推荐 GitHub 上有趣有用的项目,一分钟 get 一个优秀的开源项目,挖掘开源的价值,欢迎关注。 在全球化的今天,跨语言交流已成为日常需求,然…...

MySQL基础之DML
MySQL基础之DML 语法不区分大小写 分类 DD(definition)L 定义DM(manipulation)L 操作DQ(query)L 查询DC(control)L 控制 添加数据 # 指定字段添加数据(一条)insert into 表名(字段1,字段2,...) values(值1,值2,...);# 全部字段添加数据(一条)insert into 表名 values(值1,值…...

男单新老对决:林诗栋VS马龙,巅峰之战
听闻了那场激动人心的新老对决,不禁让人热血沸腾。在这场乒乓球的巅峰之战中,林诗栋与马龙的对决无疑是一场视觉与技术的盛宴。 3:3的决胜局,两位选手的每一次挥拍都充满了策略与智慧,他们的每一次得分都让人心跳加速。 林诗栋&am…...

Java如何判断堆区中的对象可以被回收了?
如何判断堆区中的对象可以被回收了 在Java中,垃圾回收机制会帮助我们自动回收不再被使用的对象,已到达即使释放内存的效果,但是Java又是怎么知道哪些对象不会再被我们继续使用了呢,希望你通过本篇文章,理解引用计数法与…...

.Net 6.0 监听Windows网络状态切换
上次发了一个文章获取windows网络状态,判断是否可以访问互联网。传送门:获取本机网络状态 这次我们监听网络状态切换,具体代码如下: public class WindowsNetworkHelper {private static Action<bool>? _NetworkStatusCh…...

UE4 材质学习笔记01(什么是着色器/PBR基础)
1.什么是shader 着色器是控制屏幕上每个像素颜色的代码,这些代码通常在图形处理器上运行。 现如今游戏引擎使用先进的基于物理的渲染和照明。而且照明模型模型大多数是被锁定的。 因此我们创建着色器可以控制颜色,法线,粗糙度,…...

算法 | 位运算(哈希思想)
位运算 &与两个位都为1时,结果才为1(有0为0)|或两个位都为0时,结果才为0(有1为1)^异或两个位相同为0,相异为1~取反0变1,1变0<<左移各二进位全部左移若干位,高…...

前端提升方向
1、脚手架配置:首先你会发现,一旦团队项目里多个项目之间的配置或者规范不同步,那么每个项目的配置都需要手动修改,而这很浪费时间。所以,你可以发起了一个团队的脚手架项目,把项目中的代码规范、Vite 配置…...

深度学习基础—残差网络ResNets
1.残差网络结构 当网络训练的很深很深的时候,效果是否会很好?在这篇论文中,作者给出了答案:Deep Residual Learning for Image Recognitionhttps://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/He_Deep_Residual_…...

鸿蒙ArkUI实战开发-主打自研语言及框架
ArkUI 是 HarmonyOS 的声明式 UI 开发框架,而 ArkUI-X 是基于 ArkUI 框架扩展而来的跨平台开发框架。ArkUI-X 支持 HarmonyOS、OpenHarmony、Android 和 iOS 平台,允许开发者使用一套代码构建支持多平台的应用程序。 一、ArkUI-X 的实战开发步骤 在实战开…...

HDU Sit sit sit (区间DP+组合数)
题目大意:有 n 张椅子,n 个人,所有人都可以按照任意顺序坐在任意一张椅子上,但是同时满足这三种情况的椅子不能坐: 1.椅子上有左右两张相邻的椅子。 2.左右相邻的椅子不是空的。 3.左右相邻的椅子颜色不同。 如果当前学…...

Qt开发技巧(十四)文字的分散对齐,设置动态库路径,进度条控件的文本,文件对话框的卡顿,滑块控件的进度颜色,停靠窗体的排列,拖拽事件的坑
继续讲一些Qt开发中的技巧操作: 1.文字的分散对齐 有时候需要对文本进行分散对齐显示,相当于无论文字多少,尽可能占满整个空间平摊占位宽度,但是在对支持对齐方式的控件比如QLabel调用 setAlignment(Qt::AlignJustify | Qt::Align…...

VirtulBOX Ubuntu22安装dpdk23.11
目录 依赖包安装 Python安装 numa安装 编辑Python pip3安装 编辑pyelftools安装 meson和ninja安装 编辑构建与编译 Meson构建DPDK 编辑Ninja安装DPDK 编辑VFIO-PCI驱动安装 大页内存和IOMMU配置 编辑VFIO-PCI加载 编辑VFIO-PCI驱动绑定 编辑dpdk…...

线性代数书中求解齐次线性方程组、非齐次线性方程组方法的特点和缺陷(附实例讲解)
目录 一、克拉默法则 1. 方法概述 2. 例16(1) P45 3. 特点 (1) 只适用于系数矩阵是方阵 (2) 只适用于行列式非零 (3) 只适用于唯一解的情况 (4) 只适用于非齐次线性方程组 二、逆矩阵 1. 方法概述 2. 例16(2) P45 3. 特点 (1) 只适用于系数矩阵必须是方阵且可逆 …...

初识算法 · 双指针(2)
目录 前言: 盛最多水的容器 题目解析: 算法原理: 算法编写: 有效三角形的个数 题目解析: 算法原理: 算法编写: 前言: 本文介绍两个题目,盛最多水的容器和有效三…...

React常见面试题目
React常见面试题目详解包括以下几个方面: 1. 对React的理解及特性 定义与用途:React是一个用于构建用户界面的JavaScript库,它遵循组件设计模式、声明式编程范式和函数式编程概念,使得前端应用程序更高效。 核心特性: …...

图解网络OSI模型与TCP/IP
一、OSI模型与TCP/IP 1、OSI模型 OSI/RM(Open System Interconnection,开放系统互联参考模型)是由ISO(国际标准组织)创建的一个有助于开放和理解计算机的通信模型,OSI七层参考模型作为一套规范的标准&…...

15分钟学 Python 第31天 :Web Scraping
Day 31:Web Scraping 1. Web Scraping 概述 Web Scraping(网页抓取)是一种自动提取网站数据的技术。它常用于从网页中收集信息,对数据进行分析和处理。无论是获取产品价格、市场调研,还是收集新闻信息,We…...