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

图解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 使用场景

协变的常见使用场景包括:

  1. 委托:在使用委托时,协变允许将一个返回派生类的委托赋值给返回基类的委托。
  2. LINQ:在LINQ查询中,使用协变来处理不同类型的集合。
  3. 事件处理:在事件处理程序中,使用协变来处理不同类型的事件。

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}}
}

使用协变时,应该注意以下几点:

  1. 只能在返回值中使用协变,而不能在方法参数中使用。
  2. 协变使得代码更加灵活,但也可能引入类型安全问题,因此在使用时应谨慎。

2. 逆变

在C#中,逆变(Contravariance)是与协变相反的特性,允许将基类类型替换为派生类类型。逆变主要用于参数类型的上下文,特别是在方法参数时。通过逆变,我们可以使用更通用的类型来替代特定的类型,从而提高代码的灵活性和可重用性。

2.1 逆变的概念

逆变是指在泛型类型的使用中,允许将某个类型参数替换为该参数的基类。这种特性通常在需要处理不同类型的对象时非常有用。表现在代码上就是某个函数的参数类型是基类类型,但是可以接受其派生类类型的实参。

2.2 语法

在C#中,可以通过使用 in 关键字来声明逆变类型参数。下面是逆变的基本语法:

public delegate void MyDelegate<in T>();

2.3 使用场景

逆变的常见使用场景包括:

  1. 委托:在使用委托时,逆变允许将一个接受派生类的委托赋值给接受基类的委托。
  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#高级教程(四):协变、逆变

本章的主题是可变性&#xff08;variance&#xff09;&#xff0c;这里的可变性更多的是指基类和派生类之间的转换。可变性分为三种&#xff1a;协变&#xff08;covariance&#xff09;、逆变&#xff08;contravariance&#xff09;和不变&#xff08;invariance&#xff09;…...

详解CSS中的伪元素

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

paper_template

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

【Bug】解决 Ubuntu 中 “error: Unable to Find Python3 Executable” 错误

解决 Ubuntu 中 “Unable to Find Python3 Executable” 错误 在 Ubuntu 系统上使用 Python 进行开发时&#xff0c;遇到找不到 python3 可执行文件的错误。 主要问题是无法正常打开终端&#xff08;原生与terminator&#xff09;&#xff0c;找不到python3&#xff0c;且无法…...

CUDA与TensorRT学习六:模型部署-CNN、模型部署-YOLOv8检测器、部署BEVFusion模型

文章目录 一、模型部署-CNN二、模型部署-YOLOv8检测器三、部署BEVFusion模型 一、模型部署-CNN 二、模型部署-YOLOv8检测器 三、部署BEVFusion模型...

防sql注入的网站登录系统设计与实现

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

如何快速切换电脑的ip地址

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

鸿蒙HarmonyOS之选择相册文件(照片/视频)方法

一、新建文件工具类FileUtil.ets 包含&#xff1a;选择照片方法、获取文件类型方法、去除后缀、获取后缀方法 import { BusinessError, request } from kit.BasicServicesKit; import photoAccessHelper from ohos.file.photoAccessHelper; import bundleManager from ohos.b…...

【QT Qucik】C++交互:接收QML信号

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

【C++】关键字+命名空间

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

网络层——IP

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

随笔 漫游互联网

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

8.9K Star,开源自托管离线翻译引擎

Hi&#xff0c;骚年&#xff0c;我是大 G&#xff0c;公众号「GitHub 指北」会推荐 GitHub 上有趣有用的项目&#xff0c;一分钟 get 一个优秀的开源项目&#xff0c;挖掘开源的价值&#xff0c;欢迎关注。 在全球化的今天&#xff0c;跨语言交流已成为日常需求&#xff0c;然…...

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马龙,巅峰之战

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

Java如何判断堆区中的对象可以被回收了?

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

.Net 6.0 监听Windows网络状态切换

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

UE4 材质学习笔记01(什么是着色器/PBR基础)

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

算法 | 位运算(哈希思想)

位运算 &与两个位都为1时&#xff0c;结果才为1&#xff08;有0为0&#xff09;|或两个位都为0时&#xff0c;结果才为0&#xff08;有1为1&#xff09;^异或两个位相同为0&#xff0c;相异为1~取反0变1&#xff0c;1变0<<左移各二进位全部左移若干位&#xff0c;高…...

前端提升方向

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

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)

2025年能源电力系统与流体力学国际会议&#xff08;EPSFD 2025&#xff09;将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会&#xff0c;EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

以光量子为例,详解量子获取方式

光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学&#xff08;silicon photonics&#xff09;的光波导&#xff08;optical waveguide&#xff09;芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中&#xff0c;光既是波又是粒子。光子本…...

如何配置一个sql server使得其它用户可以通过excel odbc获取数据

要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据&#xff0c;你需要完成以下配置步骤&#xff1a; ✅ 一、在 SQL Server 端配置&#xff08;服务器设置&#xff09; 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到&#xff1a;SQL Server 网络配…...