型变 型变(variance)是类型系统里的概念,包括协变(covariance)、逆变(contravariance)和不变(invariance)。 在 Scala 中在类型参数前添加 + 代表参数化类型在该类型参数上协变,添加 - 则代表逆变,什么都不加就是不变。 逆变 相对于协变,逆变显得非常不符合直觉,它表明,如果 B 是 A 的子类,那么 T[B] 反而是 T[A] 的父类。 很难想象什么地方会出现逆变的情况,而事实上,函数类型相对于其参数类型就是逆变的,Scala 中接受一个参数的函数类型声明如下: trait Function1[-T1, +R] extends AnyRef ,就是函数类型在其返回值的类型上协变,在其参数类型上逆变。
in关键字指明类型参数是逆变的 逆变:在类型参数前加in 基类对象的引用期望的是传入到基类对象,但实际上(也允许它)传入到派生对象,这叫做逆变 这样可以工作,因为在调用的时候,调用代码传入了派生类型的变量 “逆变”->”逆常的变”->”不正常的变化”->object->string 逆变。 协变(out)是将派生类对象的引用传入到基类对象,输出派生类的值 逆变(in)是将基类对象的引用传入到派生对象,派生对象只能操作基类部分 接口的协变和逆变 1️⃣ 接口的协变 using System DoSomething(father); } } 3️⃣ 协变和逆变的隐式强制转换 编译器自动识别某个已构建的委托是协变或是逆变并且自动进行强制转换 using System 这些类型参数不能用于协变或逆变 delegate T Factory< out R, in S, T >(); // 协变 逆变 不变 大家还有什么问题,欢迎在下方留言
msdn 解释如下: “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。 “逆变”则是指能够使用派生程度更小的类型。 解释的很正确,大致就是这样,不过不够直白。 直白的理解: “协变”->”和谐的变”->”很自然的变化”->string->object :协变。 “逆变”->”逆常的变”->”不正常的变化”->object->string 逆变。 上面是个人对协变和逆变的理解,比起记住那些派生,类型,原始指定,更大,更小之类的词语,个人认为要容易点。 我个人的理解: 因为协变和逆变的英文太复杂了,并没有体现协变和逆变的不同,但是out 和 in 却很直白。 上面演示的是协变,接下来要演示下逆变。
型变 型变是指类型转换后的继承关系。 Kotlin 的型变分为逆变、协变和不变。 2.1 协变 如果 A 是 B 的子类型,并且Generic 也是 Generic 的子类型,那么 Generic<T> 可以称之为一个协变类。 2.2 逆变 如果 A 是 B 的子类型,并且 Generic 是 Generic 的子类型,那么 Generic<T> 可以称之为一个逆变类。 super T> Java 的逆变通过下界通配符实现。 下面的代码因为是协变的,无法添加新的对象。 这意味着它们既不是协变的也不是逆变的。 例如 MutableList,它可读可写,泛型没有使用in、out。 三. 总结 本文从 Kotlin 的类、类型引出了型变。
这种“型变”分为两种,一种是子类型可以赋值给父类型,叫做协变,一种是父类型可以赋值给子类型,叫做逆变。 这就是逆变,函数的参数有逆变的性质(而返回值是协变的,也就是子类型可以赋值给父类型)。 那反过来呢,如果 printHoobies 赋值给 printName 会发生什么? 父子类型之间自然应该能赋值,也就是会发生型变。 型变分为逆变和协变。协变很容易理解,就是子类型赋值给父类型。 不过 ts 2.x 之前反过来依然是可以赋值的,也就是既逆变又协变,叫做双向协变。 理解了如何判断父子类型(结构类型系统),父子类型的型变(逆变、协变、双向协变),很多类型兼容问题就能得到解释了。
之后又想到了其他一些相关的东西,除了简单地分析如何通过Emit实现EventHandler的类型转换之外,还加上关于Delegate“协变”与“逆变”的一些东西,算是对前一篇文章的完善。 目录 一、从Delegate的“协变”与“逆变”说起 二、EventHandler<TEventArgs>是否换一种定义方式更好? 四、通过Emit实现EventHandler的类型转换 五、最简单的转换方式 一、从Delegate的“协变”与“逆变”说起 根据Delegate“协变”与“逆变”的原理,对于两个具有相同声明的两个 我们在定义泛型Delegate的时候可以利用C#“协变”与“逆变”,使类型为A对象能够赋值给类型为B的变量。 如果事件类型对于得Delegate并没有采用逆变方式定义,那么要求我们注册一个与之类型完全一致的Delegate。
这种子类到父类的转换就是协变。而另外一种类似于父类转向子类的变换,可以简单的理解为逆变。逆变协变可以用于泛型委托和泛型接口,本篇文章我们将讲解C#里逆变和协变的使用。 逆变和协变的语法第一次接触难免感到陌生,最好的学习方式就是在项目中多去使用,相信会有很多感悟。 协变与逆变 协变(共变):泛型委托或泛型接口的类似于父类转向子类的变换; 逆变(反变):泛型委托或泛型接口的类似子类到父类的隐式转换; 逆变与协变用来描述类型转换后的继承关系,其定义:如果A、B表示类型 ,和string与Object的父子关系是相反的; 协变和逆变能够实现数组类型、委托类型和泛型类型参数的隐式引用转换。 InterfaceFuncObject = InterfaceFuncInt;//编译失败,值类型不参与协变或逆变 //泛型接口-逆变 InterfaceFuncB<object
现代JavaScript高级小册 深入浅出Dart 现代TypeScript高级小册 类型兼容:协变和逆变 引言 在类型系统中,协变和逆变是对类型比较(类型兼容)一种形式化描述。 在其他一些类型系统中,例如 TypeScript,协变和逆变的规则是隐式嵌入的,通过类型兼容性检查来实现。 协变和逆变的存在使得类型系统具有更大的灵活性。 这就是协变。反过来,如果你有一个处理Animal类型对象的函数,并且你有一个Dog类型的对象,你应该可以使用这个函数来处理Dog对象。这就是逆变。 协变和逆变还可以帮助我们创建更通用的代码。 (Contravariance) 逆变是协变的反面。 因此,函数的参数类型是逆变的。 逆变:类型的向上兼容性 逆变描述的是类型的“向上兼容性”。
导读 泛型是Java最基础的语法之一,众所周知:出于安全原因,泛型默认不能支持型变(否则会引入危险),因此Java提供了通配符上限和通配符下限来支持型变,其中通配符上限就泛型协变,通配符下限就是泛型逆变 逆变:通配符下限 Java引入了通配符下限语法是为支持逆变(controvariance):如果A是B的父类,那么List反而相当于是List<? super Integer>的子类——这种型变方式被称为逆变(contravariance)。 对于支持逆变的泛型集合,例如List<? 但如果程序尝试从泛型逆变的集合中取出元素,那么取出的元素只能被当成Object处理(众生皆Object)。 总结来说,支持逆变的集合只能添加元素,不能取出元素(除非取出元素都当成Object)——疯狂Java讲义归纳的口诀是:逆变只进不出!
写在前面 ---- 和小伙伴分享一些java小知识点,主要围绕下面几点: 什么是逆变(contravariant)&协变(covariant)? 数组支持协变&逆变吗? 泛型支持协变&逆变吗? 不支持逆变,那泛型呢? 对于协变和逆变是否支持 泛型不支持协变也不支持逆变,即不能把一个父类对象赋值给一个子类类型变量,相反也是同理。 所以直接编译报错,即泛型不支持协变也不支持逆变. ,即泛型可以通过super和extends来模拟实现协变和逆变,但是本身是不存在协变和逆变的,这里主要利用了泛型在编译器有效 List< ?
本文简述了 C# 中协变和逆变的一些知识 在 C# 中, 协变 和 逆变 能够实现 数组类型 和 委托类型 的隐式引用转换, .NET Framework 4 (包括)以后, C# 也开始支持在 泛型接口 和 泛型委托 中使用协变和逆变,下面的内容也主要围绕泛型类型参数的协变和逆变来进行讲解. ,这点上逆变和协变其实是一致的. 小结: 协变和逆变用于隐式引用转换 协变的关键字为 out,被其修饰的参数类型只能用于输出参数 逆变的关键字为 in,被其修饰的参数类型只能用于输入参数 子类总是可以安全的转为父类是保证协变和逆变类型安全的统一前提 参考资料 协变和逆变 (C#) 泛型中的协变和逆变 深入理解 C# 协变和逆变 理解 C# 泛型接口中的协变与逆变
背景 文接上回,说到过Java的泛型擦除问题,这块我又联想到一个有意思的考点泛型的协变和逆变。 二、逆变 <? super T>,超类型限定。 逆变同样也是在各类源码中层出不穷,结合协变的理解,这块相信大家应该不难理解。 常见的例子比如,List<? 通俗理解生产者为协变,消费者为逆变。 针对于生产者,可取,有上界;针对于消费者,可存,有下界。 理解它,也可以通过Java的继承关系, 假设存在继承关系Object-》T、T-》A、T-》B; 即T为A、B的父类,协变面向子类;逆变面向Object,它是所有对象的父类。 小结 1、协变、逆变的区别要分清 2、另外,再提一点泛型和通配符的区别,当然也可以结合第一点理解, <T extends AAA>用于定义泛型类和方法,擦除后为AAA类型; <?
—沃茨•其索特 1 什么是协变与逆变 刚开始看到协变(Covariance)和逆变(Contravariance)的时候,差点晕菜,反复查了一些资料,才稍有些自己的体会,难免有理解不对的地方,欢迎指出 逆变:如果说List<Cat> 是 List<Animal>的supertype,也就是衍生类型的关系和原来类型( Cat 与 Animal)的关系是相反的,那我们就说 List 是和它的原来类型逆变( 2 为什么要了解协变与逆变? 也就是说函数的参数是逆变的。 得到的结论是: 函数的参数是逆变的,返回值是协变的。 我们知道了变化的规则,就能判断出类型的关系,就可以知道一个类型是否可以替换另外一个类型。 其他类型的协变和逆变 上面我们提到了函数的参数和返回值的分别是逆变和协变,在 Swift 中除了函数,还有属性(property),范型(Generic)等。
前言 在引用类型系统时,协变、逆变和不变性具有如下定义。 这些示例假定一个名为 Base 的基类和一个名为 Derived的派生类。 固定泛型类型参数既不是协变,也不是逆变。 你无法将 List 的实例分配给 List 类型的变量,反之亦然。 以上来自于官方文档对协变、逆变、不变性的解释 为啥C#需要协变和逆变? (Contravariance) 内置的泛型逆变委托Action、Func 、Predicate,内置的泛型逆变接口IComparable<T>、IEquatable<T>: public delegate 、方法或者委托的输入参数 当要进行类型转换,占位符T要转换的目标类型也必须是其子类,上述例子则是FooBase转为Foo 总结 协变和逆变只对泛型委托和泛型接口有效,对普通的泛型类和泛型方法无效 协变和逆变的类型必须是引用类型 ,因为值类型不具备继承性,因此类型转换存在不兼容性 泛型接口和泛型委托可同时存在协变和逆变的类型参数,即占位符T 参考 泛型中的协变和逆变 | Microsoft Docs 《你必须知道的.NET(第2
前言 为什么需要引入逆变、协变和双向协变这些概念 因为考虑到类型兼容,详情参考https://www.typescriptlang.org/docs/handbook/type-compatibility.html animal 实例上缺少属性 'bark' 协变和逆变 如何处理类型兼容呢? 通过协变和逆变原则 协变与逆变(covariance and contravariance)是在计算机科学中,描述具有父/子型别关系的多个型别通过型别构造器、构造出的多个复杂型别之间是否有父/子型别关系的用语 维基百科上关于协变和逆变的解释有点晦涩难懂。 这里,我们用更通俗一点的语言来表述: 协变:允许子类型转换为父类型(可以里式替换LSP原则进行理解) 逆变:允许父类型转换为子类型 逆变 // Dog ≼ Animal var feedAnimal
1.2 逆变 逆变指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型。如 object 到 string 的转换。多见于类型参数用作方法的输入值。 泛型类型参数支持协变和逆变,可在分配和使用泛型类型方面提供更大的灵活性。 2. 数组也继承了这一特性,对于一个string[]类型而言 理解了上述概念后,让我们来看看协变和逆变的概念,这里我们只谈谈关于接口可变性中的一些内容。以下我简单给出一个接口及其实现。 也正是因此,为了防止开发者写出错误的代码,.net 设计者便用了协变和逆变(对应 out 和 in 关键字)来强制要求正确行为。 所以归根到底,协变和逆变只是一种约束而已,这种规范限制了你的泛型接口中要么只能有将类型参数当作返回值的协变相容方法(加了 out 关键字),要么只能有将类型参数当作输入值的逆变相容方法(加了 in 关键字
背景 文接上回,说到过Java的泛型擦除问题,这块我又联想到一个有意思的考点泛型的协变和逆变。 二、逆变 <? super T>,超类型限定。 逆变同样也是在各类源码中层出不穷,结合协变的理解,这块相信大家应该不难理解。 常见的例子比如,List<? 通俗理解生产者为协变,消费者为逆变。 针对于生产者,可取,有上界;针对于消费者,可存,有下界。 理解它,也可以通过Java的继承关系, 假设存在继承关系Object-》T、T-》A、T-》B; 即T为A、B的父类,协变面向子类;逆变面向Object,它是所有对象的父类。 小结 1、协变、逆变的区别要分清。 2、另外,再提一点泛型和通配符的区别,当然也可以结合第一点理解, <T extends AAA>用于定义泛型类和方法,擦除后为AAA类型; <?
__covariant(协变):用于泛型数据强转类型,可以向上强转,子类可以转成父类。 __contravariant(逆变):用于泛型数据强转类型,可以向下强转,父类可以转成子类。 例如: // __covariant 协变,子类转父类;泛型名字是ObjectType @interface Person<__covariant ObjectType> :NSObject // 语言 Language : NSObject @end @interface Java : Language @end @interface iOS : Language @end 首先是没有添加协变和逆变 } 添加了逆变 // __contravariant 可以逆变,父类转子类 泛型名字是ObjectType @interface Person<__contravariant ObjectType language; // iOS 只会iOS Person<iOS *> *p1 = [[Personalloc]init]; p1 = p; // 如果没添加逆变会报指针类型错误警告
协变 协变: 当类型参数仅用作输出(即返回值)时,可将类型参数标记为 out 这样就允许 Del<Dog> dogMaker = MakeDog; // MakeDog是一个返回Dog匹配委托的方法 期望返回 Animal , 而其指向的 Del<Dog> 返回了 Dog , Dog 是 Animal 的派生类,接受返回 Dog ,于是可以,调用代码可以自由的操作返回的对象的 Animal 部分 逆变 逆变: 当类型参数仅用作输入(作为方法的参数类型),可将类型参数标记为in 这样就允许 Del<Animal> animal =ActOnAnimal; // ActOnAnimal为匹配此委托的方法 ,于是又传入了其指向了 Del<Animal> , 于是传入 Del<Animal> 了一个Animal的派生类Dog 这种在期望传入基类 <Animal> 时允许传入派生对象 <Dog> 的特性叫做逆变 本文作者: yiyun 本文链接: https://moeci.com/posts/2022/01/CSharp-泛型委托的协变与逆变/ 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA
在之前的文章中我们简单的介绍过scala中的协变和逆变,我们使用+ 来表示协变类型;使用-表示逆变类型;非转化类型不需要添加标记。 同样的道理如果我们定义一个class C[-A] {}, 这里A的类型是逆变的,这就意味着在方法需要参数是C[String]的时候,我们可以用C[AnyRef]来代替。 函数的参数和返回值 现在我们讨论scala中函数参数的一个非常重要的结论:函数的参数必须是逆变的,而返回值必须是协变的 为什么呢? 如果函数的参数使用了协变,返回值使用了逆变则会编译失败: scala> trait MyFunction2[+T1, +T2, -R] { | def apply(v1:T1, v2:T2): R = 如果可变参数是逆变的ContainerPlus[-A],那么对于: val cm: ContainerMinus[C] = new ContainerMinus(new CSuper) 定义的类型是C,