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

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…...

【JavaScript】《JavaScript高级程序设计 (第4版) 》笔记-附录B-严格模式

附录B、严格模式 严格模式 ECMAScript 5 首次引入严格模式的概念。严格模式用于选择以更严格的条件检查 JavaScript 代码错误&#xff0c;可以应用到全局&#xff0c;也可以应用到函数内部。严格模式的好处是可以提早发现错误&#xff0c;因此可以捕获某些 ECMAScript 问题导致…...

跨平台 C++ 程序崩溃调试与 Dump 文件分析

前言 C 程序在运行时可能会由于 空指针访问、数组越界、非法内存访问、栈溢出 等原因崩溃。为了分析崩溃原因&#xff0c;我们通常会生成 Dump 文件&#xff08;Windows 的 .dmp&#xff0c;Linux 的 core&#xff0c;macOS 的 .crash&#xff09;&#xff0c;然后用调试工具分…...

缺陷VS质量:为何软件缺陷是质量属性的致命对立面?

为何说缺陷是质量的对立面&#xff1f; 核心逻辑&#xff1a;软件质量的定义是“满足用户需求的程度”&#xff0c;而缺陷会直接破坏这种满足关系。 对立性&#xff1a;缺陷的存在意味着软件偏离了预期行为&#xff08;如功能错误、性能不足、安全性漏洞等&#xff09;&#…...

伍[5],伺服电机,电流环,速度环,位置环

电流环、速度环和位置环是电机控制系统中常见的三个闭环控制环节,通常采用嵌套结构(内环→外环:电流环→速度环→位置环),各自负责不同层级的控制目标。以下是它们的详细说明及相互关系: 1. 电流环(最内环) 作用:控制电机的电流,间接控制输出转矩(τ=Kt⋅Iτ=Kt​⋅…...

RuntimeError: CUDA error: device-side assert triggered

RuntimeError: CUDA error: device-side assert triggered 欢迎来到英杰社区&#xff0c;这里是博主英杰https://bbs.csdn.net/topics/617804998 原因&#xff1a; cuda运行可能是异步的&#xff08;asynchronously&#xff09;&#xff0c;因此报错信息中提示的位置可能不准确…...

清华大学Deepseek第六版AIGC发展研究3.0(共186页,附PDF下载)

人工智能生成内容&#xff08;AIGC&#xff09;正以前所未有的速度改变我们的生活。 2024年底&#xff0c;清华大学新闻与传播学院与人工智能学院联合发布了《AIGC发展研究3.0版》&#xff0c;这份报告系统梳理了AIGC技术的突破性进展、应用场景及社会影响&#xff0c;并展望了…...

SpringBoot生成唯一ID的方式

1.为什么要生成唯一ID&#xff1f; 数据唯一性&#xff1a;每个记录都需要有一个独一无二的标识符来确保数据的唯一性。这可以避免重复的数据行&#xff0c;并有助于准确地查询、更新或删除特定的记录。 数据完整性&#xff1a;通过使用唯一ID&#xff0c;可以保证数据库中的数…...

通俗易懂的分类算法之K近邻详解

通俗易懂的分类算法之K近邻详解 用最通俗的语言和例子&#xff0c;来彻底理解 K近邻&#xff08;K-Nearest Neighbors&#xff0c;简称 KNN&#xff09; 这个分类算法。不用担心复杂的数学公式&#xff0c;我会用生活中的例子来解释&#xff0c;保证你一听就懂&#xff01; 1.…...

CSDN markdown 操作指令等

CSDN markdown 操作指令等 页内跳转 [内容](#1) <div id"1"> </div>...

【linux】文件与目录命令 - uniq

文章目录 1. 基本用法2. 常用参数3. 用法举例4. 注意事项 uniq 命令用于过滤文本文件中相邻的重复行&#xff0c;并支持统计重复次数或仅保留唯一行。它通常与 sort 命令配合使用&#xff0c;因为 uniq 只识别相邻的重复行。 1. 基本用法 语法&#xff1a; uniq [选项] [输入…...

零信任沙箱:为网络安全筑牢“隔离墙”

在数字化浪潮汹涌澎湃的今天&#xff0c;网络安全如同一艘船在波涛汹涌的大海中航行&#xff0c;面临着重重挑战。数据泄露、恶意软件攻击、网络钓鱼等安全威胁层出不穷&#xff0c;让企业和个人用户防不胜防。而零信任沙箱&#xff0c;就像是一座坚固的“隔离墙”&#xff0c;…...

【金融量化】Ptrade中交易环境支持的业务类型

1. 普通股票买卖 • 特点&#xff1a; 普通股票买卖是最基础的交易形式&#xff0c;投资者通过买入和卖出上市公司的股票来获取收益。 ◦ 流动性高&#xff1a;股票市场交易活跃&#xff0c;买卖方便。 ◦ 收益来源多样&#xff1a;包括股价上涨的资本利得和公司分红。 ◦ 风险…...

【Java---数据结构】链表 LinkedList

1. 链表的概念 链表用于存储一系列元素&#xff0c;由一系列节点组成&#xff0c;每个节点包含两部分&#xff1a;数据域和指针域。 数据域&#xff1a;用于存储数据元素 指针域&#xff1a;用于指向下一个节点的地址&#xff0c;通过指针将各个节点连接在一起&#xff0c;形…...

紧跟 Web3 热潮,RuleOS 如何成为行业新宠?

Web3 热潮正以汹涌之势席卷全球。从金融领域的创新应用到供应链管理的变革&#xff0c;从社交媒体的去中心化尝试到游戏产业的全新玩法探索&#xff0c;Web3 凭借其去中心化、安全性和用户赋权等特性&#xff0c;为各个行业带来了前所未有的机遇。在这股热潮中&#xff0c;Rule…...

CC++的内存管理

目录 1、C/C内存划分 C语言的动态内存管理 malloc calloc realloc free C的动态内存管理 new和delete operator new函数和operator delete函数 new和delete的原理 new T[N]原理 delete[]的原理 1、C/C内存划分 1、栈&#xff1a;存有非静态局部变量、函数参数、返回…...

Spark核心之02:RDD、算子分类、常用算子

spark内存计算框架 一、目标 深入理解RDD弹性分布式数据集底层原理掌握RDD弹性分布式数据集的常用算子操作 二、要点 ⭐️1. RDD是什么 RDD&#xff08;Resilient Distributed Dataset&#xff09;叫做**弹性分布式数据集&#xff0c;是Spark中最基本的数据抽象&#xff0c…...

【Resis实战分析】Redis问题导致页面timeout知识点分析

事故现象&#xff1a;前端页面返回timeout 事故回溯总结一句话&#xff1a; &#xff08;1&#xff09;因为大KEY调用量&#xff0c;随着白天自然流量趋势增长而增长&#xff0c;最终在业务高峰最高点期占满带宽使用100%。 &#xfeff; &#xfeff; &#xff08;2&#x…...

单一职责原则(设计模式)

目录 问题&#xff1a; 定义&#xff1a; 解决&#xff1a; 方式 1&#xff1a;使用策略模式 示例&#xff1a;用户管理 方式 2&#xff1a;使用装饰者模式 示例&#xff1a;用户操作 方式 3&#xff1a;使用责任链模式 示例&#xff1a;用户操作链 总结 推荐 问题&a…...

生理信号概念

rPPG 信号&#xff08;远程光电容积脉搏波信号&#xff09; 原理&#xff1a; 基于光电容积脉搏波描记法&#xff0c;利用普通摄像头&#xff0c;在一定距离外捕捉人体皮肤表面因心脏泵血导致的血液容积变化引起的细微颜色变化&#xff0c;通过图像处理和信号分析算法提取心率…...

应用层缓存的庖丁解牛

“应用层缓存”常被误解为“加个 Redis 那么简单”或“为了快而快”。 但本质上&#xff0c;应用层缓存是用“空间”换“时间”&#xff0c;用“一致性风险”换“系统吞吐量”的终极权衡艺术。 它是数据库&#xff08;慢、稳、强一致&#xff09;与用户&#xff08;快、急、高并…...

从零搭建WebRTC信令服务:SpringBoot WebSocket与Vue3的实战协同

1. WebRTC信令服务基础认知 第一次接触WebRTC时&#xff0c;我被它直接建立P2P连接的能力惊艳到了——就像两个陌生人突然跳过所有中间环节直接开始面对面交流。但很快我发现&#xff0c;这种"魔法"背后需要一套精密的协调机制&#xff0c;这就是信令服务的用武之地。…...

lite-avatar形象库保姆级教学:从CSDN控制台创建GPU实例到数字人上线全过程

lite-avatar形象库保姆级教学&#xff1a;从CSDN控制台创建GPU实例到数字人上线全过程 桦漫AIGC集成开发 | 微信: henryhan1117 1. 开篇&#xff1a;为什么选择lite-avatar形象库&#xff1f; 如果你正在寻找高质量的数字人形象&#xff0c;但又不想从零开始训练模型&#xff…...

Squeezer性能优化指南:提升dApp响应速度的7个技巧

Squeezer性能优化指南&#xff1a;提升dApp响应速度的7个技巧 【免费下载链接】squeezer Squeezer Framework - Build serverless dApps 项目地址: https://gitcode.com/gh_mirrors/sq/squeezer Squeezer Framework作为构建无服务器去中心化应用(dApps)的强大工具&#…...

Seed-VC语音转换工具终极指南:零样本语音克隆技术完全解析

Seed-VC语音转换工具终极指南&#xff1a;零样本语音克隆技术完全解析 【免费下载链接】seed-vc zero-shot voice conversion & singing voice conversion, with real-time support 项目地址: https://gitcode.com/GitHub_Trending/se/seed-vc Seed-VC作为当前最先进…...

Stable-Diffusion-v1-5-archive生产环境部署:异常自动拉起+日志监控+多用户隔离方案

Stable-Diffusion-v1-5-archive生产环境部署&#xff1a;异常自动拉起日志监控多用户隔离方案 1. 引言 如果你正在寻找一个稳定、可靠、易于管理的Stable Diffusion v1.5生产环境部署方案&#xff0c;那么你来对地方了。SD1.5作为文生图领域的经典模型&#xff0c;虽然新模型…...

开源六轴机械臂从零构建指南:低成本DIY方案与实战应用

开源六轴机械臂从零构建指南&#xff1a;低成本DIY方案与实战应用 【免费下载链接】Faze4-Robotic-arm All files for 6 axis robot arm with cycloidal gearboxes . 项目地址: https://gitcode.com/gh_mirrors/fa/Faze4-Robotic-arm 开源六轴机械臂技术正以前所未有的速…...

背单词花园:把单词种进长期记忆,告别背了就忘

为什么背单词花园抗遗忘效果出众&#xff1f;因为它把艾宾浩斯遗忘曲线&#xff0c;变成了看得见、好坚持的种花流程。一、新学单词 收获种子&#xff0c;记忆从第一步就扎根每次领取种子&#xff0c;就是开启一次新单词学习。用趣味场景完成初次编码&#xff0c;让单词不再是…...

Jessibuca播放器在低代码平台中的集成实践:5分钟为你的应用添加实时视频能力

Jessibuca播放器在低代码平台中的集成实践&#xff1a;5分钟为你的应用添加实时视频能力 当企业需要快速构建内部管理系统或行业解决方案时&#xff0c;低代码平台正成为提升开发效率的利器。而视频能力作为现代应用的基础需求&#xff0c;如何在不编写复杂代码的情况下实现专业…...

ai赋能安装:让快马生成智能交互式mysql安装故障排查助手

AI赋能安装&#xff1a;让快马生成智能交互式MySQL安装故障排查助手 MySQL作为最流行的开源数据库之一&#xff0c;安装过程看似简单&#xff0c;但实际会遇到各种"坑"。新手经常被报错信息搞得一头雾水&#xff0c;老手也可能在特定环境下翻车。传统教程都是静态的…...