(八)CSharp-泛型协变和逆变(3)

news2024/11/18 23:34:50

一、协变和逆变

可变性分为三种: 协变、逆变和不变。

协变和逆变:泛型接口泛型委托添加了一个处理类型转换问题的扩展。

问题: 当两个类对象是继承与派生的关系时,由于编译器通过泛型实例化时没法确认它们之间的关系,导致派生类对象不能赋值给基类对象。反之亦然,基类对象不能赋值给派生类对象。

解决: 当泛型接口或泛型委托实例化两个对象时(基类对象和派生类对象),通过协变和逆变的方式,来解决一个对象不能赋值给另一个对象的问题。

二、协变

非泛型类的代码例子:

class Animal
{public int NumberOfLegs = 4;}

class Dog:Animal{}

class Program
{
static void Main()
{
Animal a1 = new Animal();
Animal a2 = new Dog();
}

}

赋值兼容性

每一个变量都有一种类型,你可以将派生类型的对象赋值给基类型的变量,这叫作赋值兼容性。

请添加图片描述

但是对于泛型委托就不一样了,赋值原则虽成立,但是编译器会报错,因为派生类型委托没有从基类型委托派生。两者没有继承关系。

泛型委托错误的代码例子:

    class Animal { public int Legs = 4; }
    class Dog : Animal { }

    delegate T Factory<T>();

    class Program
    {
        static Dog MakeDog()
        {
            return new Dog();
        }
        static void Main(string[] args)
        {
            Factory<Dog> dogMaker = MakeDog;//创建委托对象
            Factory<Animal> animalMaker = dogMaker;//赋值不兼容,错误

            Console.WriteLine(animalMaker().Legs.ToString());
            Console.ReadKey();
        }
    }

两个委托对象是同级的,它们都是从 delegate 类型派生,后者又派生自 Object 类型。两者之间没有派生关系,因此赋值兼容性不适用。

在泛型委托实例化之前,编译器并不知道 T 类型中的Dog 类和 Animal 类是继承关系。

解决赋值原则不适用问题:对于所有这样的情况(上面错误例子的情况),我们可以使用由派生类创建的委托类型,这样应该能够正常工作,因为调用代码总是期望到一个基类的引用。

协变: 仅将派生类型用作输出值与构造委托有效性之间的常数关系叫作协变。

通过增加 out 关键字改变委托声明:

//把 delegate T Factory<T>();改为:
delegate T Factory<out T>();
//编译通过

增加了 out T 类型之后,类型变量 T 是 Animal 类。T 此时是知道的,所以知道 Dog 派生自 Animal。

Factory<out T>():T 类型变量为 Animal 类

三、逆变

逆变: 传入基类时允许传入派生对象的特性叫作逆变。

 class Animal { public int Legs = 4; }
    class Dog : Animal { }

    class Program
    {
        //in:逆变关键字
        delegate void Action1<in T>(T a);

        static void ActionAnimal(Animal a)
        {
            Console.WriteLine(a.Legs);
        }

        static void Main(string[] args)
        {
            Action1<Animal> act1 = ActionAnimal;
            Action1<Dog> dog1 = act1;

            dog1(new Dog());
            Console.ReadKey();
        }
    }
Action1<in T>(T a):T 类型变量是 Dog 类

四、协变和逆变的不同

请添加图片描述

五、接口的协变和逆变

协变和逆变可以应用到委托上,也可以适用于接口。

    class Animal { public string Name; }
    class Dog : Animal { }

    interface IMyIfc<out T>
    {
        T GetFirst();
    }

    class SimpleReturn<T>:IMyIfc<T>
    {
        public T[] items = new T[2];

        public T GetFirst()
        {
            return items[0];
        }
    }

    class Program
    {
        static void DoSomething(IMyIfc<Animal> returner)
        {
            Console.WriteLine(returner.GetFirst().Name);
        }
        static void Main(string[] args)
        {
            SimpleReturn<Dog> dogReturner = new SimpleReturn<Dog>();
            dogReturner.items[0] = new Dog() { Name = "Avonlea" };

            IMyIfc<Animal> animalReturner = dogReturner;
            DoSomething(animalReturner);

            Console.ReadKey();
        }
    }

六、关于可变性的更多内容

除了显式的协变和逆变,编译器还可以自动识别某个已构建的委托是协变还是逆变并且进行类型强制转换。(发生在没有为对象的类型赋值的时候)

    class Animal { public int Legs = 4; }
    class Dog : Animal { }

    class Program
    {
        delegate T Factory<out T>();

        static Dog MakeDog() { return new Dog(); }

        static void Main(string[] args)
        {
            Factory<Animal> animalMaker1 = MakeDog;
            Factory<Dog> dogMaker = MakeDog;//隐式强制转换,不需要 out 也可以

            Factory<Animal> animalMaker2 = dogMaker; // 需要 out 标识符

            Factory<Animal> animalMaker3 = new Factory<Dog>(MakeDog);// 需要 out 标识符

            Console.ReadKey();
        }
    }

可变性的重要点:

  • 可变性处理的是可以使用基类型替换派生类型(或者派生类转换为基类)的安全情况。
  • 使用 in 和 out 关键字的显式变化只适用于委托和接口。
  • 不包括 in 和 out 关键字的委托和接口类型参数是不变的。
delegate T Factory<out R, in S, T>();
//out R:协变
//in S:逆变
//T:不变

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

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

相关文章

(数组) 1991. 找到数组的中间位置 ——【Leetcode每日一题】

❓1991. 找到数组的中间位置 难度&#xff1a;简单 给你一个下标从 0 开始的整数数组 nums &#xff0c;请你找到 最左边 的中间位置 middleIndex &#xff08;也就是所有可能中间位置下标最小的一个&#xff09;。 中间位置 middleIndex 是满足 nums[0] nums[1] ... num…

FTP协议详解

文章目录 1 FTP概述2 实验环境3 FTP详解3.1 文件传输过程3.2 报文格式3.3 数据连接3.4 主动模式3.5 被动模式3.6 匿名服务器 4 总结 1 FTP概述 FTP为File Transfer Protocol的缩写&#xff0c;即文件传输协议&#xff0c;是TCP/IP 协议族中的协议之一。FTP是一个用于在计算机网…

算法模板(3):搜索(3):图论提高

图论提高 最小生成树 &#xff08;1&#xff09;朴素版prim算法&#xff08; O ( n 2 ) O(n ^ 2) O(n2)&#xff09; 适用范围&#xff1a;稠密图易错&#xff1a;注意有向图还是无向图&#xff1b;注意有没有重边和负权边。从一个集合向外一个一个扩展&#xff0c;最开始只…

(文章复现)面向配电网韧性提升的移动储能预布局与动态调度策略(1)-灾前布局matlab代码

参考文献&#xff1a; [1]王月汉,刘文霞,姚齐,万海洋,何剑,熊雪君.面向配电网韧性提升的移动储能预布局与动态调度策略[J].电力系统自动化,2022,46(15):37-45. 1.基本原理 1. 1 目标函数 本文以最恶劣光伏出力场景下的移动储能配置成本与负荷削减成本最小为目标&#xff0c;建…

(数组) 724. 寻找数组的中心下标 ——【Leetcode每日一题】

❓724. 寻找数组的中心下标 难度&#xff1a;简单 给你一个整数数组 nums &#xff0c;请计算数组的 中心下标 。 数组 中心下标 是数组的一个下标&#xff0c;其左侧所有元素相加的和等于右侧所有元素相加的和。 如果中心下标位于数组最左端&#xff0c;那么左侧数之和视为…

2023-6-9

1.网络训练&#xff1a; 在训练前先要看看读取数据的时间&#xff08;常见的性能瓶颈&#xff09;2.import dis dis 是 Python 内置的一个模块&#xff0c;其全称为 “Disassembler for Python bytecode”&#xff0c;用于反汇编 Python 字节码。它可以将 Python 代码编译成字…

视频换天造物实践秒变科幻大片实践记录

视频换天造物实践秒变科幻大片实践记录&#xff0c;过程中遇到些坑&#xff0c;结果还是相当震撼 预装软件&#xff1a; matplotlib scikit-image scikit-learn scipy numpy torch torchvision opencv-python opencv-contrib-python 安装使用的时候可能碰上scikit-image 新版…

傅里叶级数简介

先看动图 将函数f(x) 用 sin(nx) cos(nx) 的形式表示出来的方式就是傅里叶级数 这里有几个使用条件 收敛性&#xff1a;符合迪力克雷收敛条件。简单理解为 f(x) 必须是一个丝滑的曲线。周期性&#xff1a; f(x) 必须是一个周期函数 还有一个基础条件&#xff0c;三角函数具…

element-plus布局排版问题总结(更新ing)

文章目录 el-container空隙修改app组件 el-container空隙 源码-更改了容器的显示&#xff0c;占满屏幕 <template><div class"common-layout"><el-container><el-header><el-row class"el-row1"><el-col :span"12&…

oppo r11 升级8.1系统 图文教程

Time: 2023年6月11日13:39:25 By:MemroyErHero 1 预留一定的空间,存放刷机包. 2 导入刷机包 r11.ozip 到手机上 3 手机文件管理器 打开 r11.ozip 文件 4 点击立即更新即可 5 重要的事情说三遍,刷机过程中 不能关机 不能断电 否则会变成砖头 重要的事情说三遍,刷机过程中 …

cmake 基本使用

目录 CMake都有什么? 使用cmake一般流程为&#xff1a; 1 生成构建系统 使用命令在build外编译代码: cmake基本语法 指定使用最低版本的cmake 指定项目名称 指定生成目标文件的名称 指定C版本 cmake配置文件使用 cmake配置文件生成头文件 版本号定义方法一: 版本号定…

软件测试正在面试银行的可以看下这些面试题

前言 最近呢有很多的小伙伴问我有没有什么软件测试的面试题&#xff0c;由于笔者之前一直在忙工作上的事情&#xff0c;没有时间整理面试题&#xff0c;刚好最近休息了一下&#xff0c;顺便整理了一些面试题&#xff0c;现在就把整理的面试题分享给大家&#xff0c;废话就不多说…

C 语言实现简单工厂模式

文章目录 1. 背景介绍2. 设计实现3. 运行测试4. 总结 1. 背景介绍 印象中&#xff0c;设计模式是由面向对象的语言(C、JAVA)才能完成的&#xff0c;而 C 语言是面向过程的语言&#xff0c;不能实现设计模式。但C 语言中有 函数指针、回调函数 等机制&#xff0c;使用这些机制便…

Java中线程的生命周期

Java中线程的生命周期 Java中线程的声明周期与os中线程的生命周期不太一样&#xff0c;java中线程有6个状态&#xff0c;见下&#xff1a; NEW: 初始状态&#xff0c;线程被创建出来但没有被调用 start() 。RUNNABLE: 运行状态&#xff0c;线程被调用了 start()等待运行的状态…

Elasticsearch:使用 Redis 让 Elasticsearch 更快

Elasticsearch 是一个强大的搜索引擎&#xff0c;可让你快速轻松地搜索大量数据。但是&#xff0c;随着数据量的增长&#xff0c;响应时间可能会变慢&#xff0c;尤其是对于复杂的查询。在本文中&#xff0c;我们将探讨如何使用 Redis 来加快 Elasticsearch 搜索响应时间。 Re…

【数据结构】常见排序算法——常见排序介绍、归并排序、各大排序复杂度和稳定性

文章目录 1.常见排序2.归并排序2.1归并排序基本思想2.2归并排序的实现2.3归并排序特性总结 3.各大排序复杂度和稳定性 1.常见排序 2.归并排序 归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种有效的排序算法,该算法是采用分治法&#xff08;Divide andCon…

商业图表工具推荐,热门商业图表工具有哪些?

在如今的商业环境下&#xff0c;数据分析和可视化是非常重要的一环。不仅可以帮助企业更好地了解自身情况&#xff0c;还能为决策提供有力支持。因此&#xff0c;选择一个好用的商业图表工具对于报表开发人员来说是非常重要的。下面将为大家介绍7款热门商业图表工具&#xff0c…

Mac电脑删除第三方软件工具CleanMyMac X

经常使用Mac的人都知道&#xff0c;Mac除了可以在AppStore下载应用程序&#xff0c;还有许多软件是需要在网页上搜索下载的第三方软件。那么这类第三方软件软件除了下载方式不同之外还有什么是和从App store下载的软件有区别的吗&#xff1f;答案是肯定的&#xff0c;那就是这些…

Docker容器进入的4种方式

Docker容器进入的4种方式 Docker容器进入的4种方式 在使用Docker创建了容器之后&#xff0c;大家比较关心的就是如何进入该容器了&#xff0c;其实进入Docker容器有好几多种方式&#xff0c;这里我们就讲一下常用的几种进入Docker容器的方法。 进入Docker容器比较常见的几种…

带你了解自动化测试只需要一分钟

目前自动化测试并不属于新鲜的事物&#xff0c;或者说自动化测试的各种方法论已经层出不穷&#xff0c;但是&#xff0c;能够明白自动化测试并很好落地实施的团队还不是非常多&#xff0c;我们接来下用通俗的方式来介绍自动化测试…… 首先我们从招聘岗位需求说起。看近期的职业…