当前位置: 首页 > 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;通过图像处理和信号分析算法提取心率…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)

目录 1.TCP的连接管理机制&#xff08;1&#xff09;三次握手①握手过程②对握手过程的理解 &#xff08;2&#xff09;四次挥手&#xff08;3&#xff09;握手和挥手的触发&#xff08;4&#xff09;状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

leetcodeSQL解题:3564. 季节性销售分析

leetcodeSQL解题&#xff1a;3564. 季节性销售分析 题目&#xff1a; 表&#xff1a;sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

云原生玩法三问:构建自定义开发环境

云原生玩法三问&#xff1a;构建自定义开发环境 引言 临时运维一个古董项目&#xff0c;无文档&#xff0c;无环境&#xff0c;无交接人&#xff0c;俗称三无。 运行设备的环境老&#xff0c;本地环境版本高&#xff0c;ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

招商蛇口 | 执笔CID,启幕低密生活新境

作为中国城市生长的力量&#xff0c;招商蛇口以“美好生活承载者”为使命&#xff0c;深耕全球111座城市&#xff0c;以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子&#xff0c;招商蛇口始终与城市发展同频共振&#xff0c;以建筑诠释对土地与生活的…...