图解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 关键字来实现,允许使用更具体的类型作为返回值。

适用场景:

  • 多态主要用于运行时行为的动态选择,允许对象通过父类接口调用不同的实现。
  • 协变主要用于数据结构和类型安全的情况下,特别是在返回类型的灵活性方面。

各位道友,码字不易,记得一键三连呐。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/54197.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

酒店构建数字化业产业—未来之窗行业应用跨平台架构

一、建设酒店产业数字化 二、酒店数字化产业目标 三、酒店数字化业务指标 四、酒店数字化管理层 五、酒店数字化数据应用 六、酒店数字化子系统 七、酒店数字化分析

Arthas sc(查看JVM已加载的类信息 )

文章目录 二、命令列表2.2 class/classloader相关命令2.2.5 sc&#xff08;查看JVM已加载的类信息 &#xff09;举例1&#xff1a;模糊搜索&#xff0c;xx包下所有的类举例2&#xff1a;打印类的详细信息举例3&#xff1a;打印出类的Field信息 本人其他相关文章链接 二、命令列…

C++ 游戏开发

C游戏开发 C 是一种高效、灵活且功能强大的编程语言&#xff0c;因其性能和控制能力而在游戏开发中被广泛应用。许多著名的游戏引擎&#xff0c;如 Unreal Engine、CryEngine 和 Godot 等&#xff0c;都依赖于 C 进行核心开发。本文将详细介绍 C 在游戏开发中的应用&#xff0…

DC00024基于ssm实验室预约管理系统java web项目web教师预约jsp预约管理系统

1、项目功能演示 DC00024基于web实验室预约管理系统ssm教室预约实验室预约管理系统java web项目MySQL 2、项目功能描述 基于ssm实验室预约管理系统分为用户和系统管理员两个角色。 2.1 系统管理员 1、系统登录 2、用户管理&#xff1a;修改个人信息、修改个人密码、教师管理…

执行力怎么培养?

执行力怎么培养&#xff1f; 并行&#xff1a;适合在初期养成习惯&#xff0c;不抱对结果的期望天才就是强迫症&#xff1a;适合中期修身&#xff1a;适合高级 并行&#xff1a;适合在初期养成习惯&#xff0c;不抱对结果的期望 在你开始做任何事情的时候&#xff0c;不要一开…

配置Scrapy项目

配置Scrapy项目是一个涉及多个步骤的过程&#xff0c;在上一篇博客中已经写了安装Scrapy、创建Scrapy项目的步骤。 接下来应该定义Item类、编写爬虫程序以及配置settings.py文件等。以下是一个详细的配置Scrapy项目的步骤&#xff1a; 一、定义Item类 在项目目录下…

2024年09月CCF-GESP编程能力等级认证C++编程一级真题解析

本文收录于专栏《C++等级认证CCF-GESP真题解析》,专栏总目录:点这里。订阅后可阅读专栏内所有文章。 一、单选题(每题 2 分,共 30 分) 第 1 题 据有关资料,山东大学于1972年研制成功DJL-1计算机,并于1973年投入运行,其综合性能居当时全国第三位。DJL-1计算机运算控制…

单调队列应用介绍

单调队列应用介绍 定义应用场景实现模板具体示例滑动窗口最大值问题描述问题分析代码实现带限制的子序列和问题描述问题分析代码实现跳跃游戏问题描述问题分析代码实现定义 队列(Queue)是另一种操作受限的线性表,只允许元素从队列的一端进,另一端出,具有先进先出(FIFO)的特…

疾风大模型气象,基于气象数据打造可视化平台

引言 随着气象数据的广泛应用&#xff0c;越来越多的行业依赖天气预报与气候分析来做出决策。从农业、航空、能源到物流&#xff0c;气象信息无时不刻影响着各行各业的运作。然而&#xff0c;气象数据本身复杂且多样&#xff0c;如何将这些数据转化为直观、易于理解的图形和信…

ubuntu配置python环境

ubuntu新版一般默认安装python3&#xff0c;22版本对应的是python3.10. 问题1&#xff1a;直接python提示没有对应命令&#xff0c;必须要使用python3 方法&#xff1a;sudo apt-get install python-is-python3问题2&#xff1a;安装pip, venv 方法&#xff1a;sudo apt insta…

WPF之UI进阶--完整了解wpf的控件和布局容器及应用

前面三篇有关WPF的基础介绍&#xff0c;分别介绍了wpf与winform的异同&#xff0c;wpf的事件生成和使用以及数据绑定。但我们还缺乏一副好的“皮囊”&#xff0c;所以从这篇开始我们来开始学习wpf的UI相关的内容&#xff0c;首当其冲的就是布局容器。 其实我们知道&#xff0c;…

人工智能发展历程

发展历程 人工智能的发展可以追溯到20世纪30年代&#xff0c;当时数理逻辑的形式化和智能可计算思想开始构建计算与智能的关联概念。1943年&#xff0c;美国神经科学家麦卡洛克和逻辑学家皮茨共同研制成功了世界上首个人工神经网络模型——MP模型&#xff0c;这为现代人工智能…

【微服务】组件、基础工程构建(day2)

组件 服务注册和发现 微服务模块中&#xff0c;一般是以集群的方式进行部署的&#xff0c;如果我们调用的时候以硬编码的方式&#xff0c;那么当服务出现问题、服务扩缩容等就需要对代码进行修改&#xff0c;这是非常不好的。所以微服务模块中就出现了服务注册和发现组件&…

基于深度学习的乳腺癌分类识别与诊断系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 乳腺癌是全球最常见的癌症之一&#xff0c;早期诊断对于治疗效果至关重要。近年来&#xff0c;深度学习技术在医学图像分析领域取得了显著进展&#xff0c;能够从大量的医学影像数据中自动学习和提…

MySQL 支持行锁还是表锁?分别有哪些优缺点?破解数据库的锁之谜:MySQL行锁与表锁的博弈

在现代应用程序开发中&#xff0c;数据库管理系统扮演着至关重要的角色。而在数据库的性能优化过程中&#xff0c;锁机制成为了一个不可忽视的话题。对于MySQL而言&#xff0c;行锁和表锁是两种常见的锁定机制&#xff0c;了解它们的优缺点将有助于我们设计更高效的数据库应用。…

Vue3.X + SpringBoot小程序 | AI大模型项目 | 饮食陪伴官

gitee平台源码 github平台源码 饮食陪伴师是一个管理饮食的原生大模型小程序&#xff0c;优势&#xff1a; 精确营养监控&#xff1a;用户记录饮食后&#xff0c;我们会计算出食用的营养成分与分量&#xff0c;并反馈给用户。饮食建议有效&#xff1a;大模型经过我们训练具备大…

Qt 中的 QListWidget、QTreeWidget 和 QTableWidget:简化的数据展示控件

Qt 中的 QListWidget、QTreeWidget 和 QTableWidget&#xff1a;简化的数据展示控件 在 Qt 的用户界面开发中&#xff0c;展示和管理数据是常见的需求。Qt 提供了丰富的控件供开发者选择&#xff0c;其中 QListWidget、QTreeWidget 和 QTableWidget 是三个高层封装控件&#x…

程计软考题2-编译、解释程序翻译阶段

(一) 编译器和解释器的工作阶段 1.编译和解释与源程序的区别 分析&#xff1a;编译和解释是语言处理的两种基本方式。 编译过程包括词法分析、语法分析、语义分析、中间代码生成、代码优化和目标代码生成等阶段&#xff0c;以及符号表管理和出错处理模块。 解释过程在词法、语…

【Kubernetes】常见面试题汇总(四十三)

目录 98. kube-apiserver 和 kube-scheduler 的作用是什么&#xff1f; 99.您对云控制器管理器了解多少&#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff09;~&#xff08;二十二&#xff09;…

网络层——IP

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