博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【转】[C# 基础知识系列]专题九 :深入理解泛型可变性
阅读量:6589 次
发布时间:2019-06-24

本文共 4581 字,大约阅读时间需要 15 分钟。

引言:

  在C# 2.0中泛型并不支持可变性的(可变性指的就是协变性和逆变性),我们知道在面向对象的继承中就具有可变性,当方法声明返回类型为Stream,我们可以在实现中返回一个FileStream的类型(里氏代换的赶脚呢),此时就存在一个隐式的转化——从FileStream类型(子类引用)——>Stream类型(父类引用),并且引用类型的数组也存在这种从子类引用——>父类引用的转化,例如string[] 可以转化为object[](即这样的代码是可以通过编译的:string[] strs =new string[3]; object[] objs =strs;),此时我们肯定会想是否泛型中的泛型参数也可以支持这样的转化呢?然而在C# 2.0中是不支持的,但是就是因为有这样的需求,所以微软也考虑到这个问题的, 所以在C# 4.0中就引入了泛型的协变逆变性。下面就具体来介绍下C# 4.0 中对协变和逆变的具体内容有哪些的。

一、协变性

  协变性指的是——泛型类型参数可以从一个派生类隐式转化为基类(派生类退化为基类)(大家可以这样记忆的,协变性即和谐的变化,生活中我们一般会说子女长的像他们的父母,这样听起来会感觉比较和谐点,这样就很容易记住协变了 --!)在C#4.0中引入out关键字来标记泛型参数支持协变性。为了更好的说明泛型的协变性,下面就以.Net类库的中public interface IEnumerable<out T>这个接口来演示一个例子来帮助大家理解泛型协变:

四个关键字:参数、派生类、退化、基类

List listobject = new List();            List
liststrs = new List
(); // AddRange方法接收的参数类型为IEnumerable
collection // 下面的代码是传入的是List
类型的参数。 // 在MSDN中可以看出这个接口的定义为——IEnumerable
。 // 所以 IEnumerable
泛型类型参数T支持协变性,所以可以 // 将List
转化为IEnumerable
(这个是继承的协变性支持的) 就说继承就行了,哎 明白了!        // 有点乱啊 List继承IEnumberable,不过咱们不是说是
才有逆变协变一说吗,那么应该是List
可以变成List
吗 // 又因为这个IEnumerable
接口委托支持协变性,所以可以把IEnumerable
转化为——>IEnumerable
类型。这句话懂了! // 所以编译器验证的时候就不会出现类型不能转化的错误了。 listobject.AddRange(liststrs); //成功 string可以成功的隐式转化为object 是为协变 liststrs.AddRange(listobject); // 出错 object无法饮食转换为string

  代码中如果使用 这代码时 liststrs.AddRange(listobject); 就会出现编译时错误(无法从List<object>转换为IEnumerable<string>,因为List<object>可以因为继承的协变性转化为IEnumerable<object>,但是因为IEnumerable<out T>不支持逆变,即从object到string的转化,所以此时就会产生下面图中的错误了。), 错误提示截图如下:

 

二、逆变性

  逆变性指的是——泛型类型参数可以从一个基类隐式转化为派生类(可以从生活中的例子来帮助大家记忆逆变的——如果说父母长的像他们的子女的话肯定觉得别扭,在高中语文中经常会找这样的语病的),在C# 4.0中引入in关键字来标记泛型参数支持逆变性.为了更好的说明泛型的逆变性,下面就以.Net类库的中接口public interface IComparer<in T>来演示一个例子来帮助大家理解泛型逆变

 

class Program    {        static void Main(string[] args)        {            List listobject = new List();            List
liststrs = new List
(); // AddRange方法接收的参数类型为IEnumerable
collection // 下面的代码是传入的是List
类型的参数。 // 在MSDN中可以看出这个接口的定义为——IEnumerable
。 // 所以 IEnumerable
泛型类型参数T支持协变性,所以可以 // 将List
转化为IEnumerable
(这个是继承支持的) // 又因为这个IEnumerable
接口委托支持协变性,所以可以把IEnumerable
转化为——>IEnumerable
类型。 // 所以编译器验证的时候就不会出现类型不能转化的错误了。 listobject.AddRange(liststrs); //成功 ////liststrs.AddRange(listobject); // 出错 IComparer objComparer = new TestComparer(); IComparer
objComparer2 = new TestComparer(); // List
类型的 liststrs变量的sort方法接收的是IComparer
类型的参数 // 然而下面代码传入的是 IComparer
这个类型的参数,要编译成功的话,必须能够转化为IComparer
这个类型 // 正是因为IComparer
泛型接口支持逆变,所以支持object转化为string类型 // 所以下面的这行代码可以编译通过,在.Net 4.0之前的版本肯定会编译错误, // 大家可以把项目的目标框架改为.Net Framework 3.5或者更加低级的版本 // 这样下面这行代码就会出现编译错误,因为泛型的协变和逆变是C# 4.0 中新增加的特性,而.Net 4.0对应于C# 4.0。 liststrs.Sort(objComparer); // 正确 // 出错 ////listobject.Sort(objComparer2); } } public class TestComparer : IComparer
{ public int Compare(object obj1,object obj2) { return obj1.ToString().CompareTo(obj2.ToString()); } }

  上面代码中如果使用 listobject.Sort(objComparer2);时,就会出现编译错误,错误原因看过上面协变中错误原因的解释应该都可以明白的,下面是错误的截图:

 

  为了进一步说明泛型的协变和逆变是在C# 4.0中(C# 4.0即对于.net Framework 4.0)的版本都不支持泛型的协变和逆变,大家从MSDN中也可以发现的。下面是一张比较的截图(大家可以自己具体去MSDN上查看的, 当版本改为3.5或更低级的版本时,看下泛型的定义是不是没有out或in关键字,即之前的版本不支持泛型的可变性):

 

OUT:协变

IN:逆变

另外一种情况outref区别使用,ref:引用方式传递参数,out:引用方式传递未初始化的参数!

 

三、协变和逆变的注意事项

  并不是所有类型都支持泛型的协变和逆变的, 下面列出泛型的协变和你逆变中值得注意和明确的地方:

  1. 只有接口委托支持协变和逆变(如Func<out TResult>,Action<in T>),类或泛型方法的类型参数都不支持协变和逆变。

  2. 协变和逆变只适用于引用类型,值类型不支持协变和逆变(因为可变性存在一个引用转换,而值类型变量存储的就是对象本身,而不是对象的引用),所以List<int>无法转化为IEnumerable<object>.但是可转换位IEnumerable<int>

  3. 必须显示用in或out来标记类型参数。

  4. 委托的可变性不要再多播委托中使用,相信这点很多人都没有注意到的, 下面我举个例子来说明下,当大家遇到这样的问题可以知道为什么:

// 下面初始化委托使用了Lambda表达式,Lambda?表达式将在后面专题向大家具体介绍            Func
stringfunc = () => ""; Func
objectfunc = () => new object(); Func combined = stringfunc + objectfunc;

 

  上面代码可以通过编译,因为泛型Func<out T>支持协变,所以将Func<string>转换为Func<object>类型,但是对象本身仍然为Func<string>类型,然而Delegate.Combine方法要求参数必须为相同类型——否则该方法无法确定要创建什么类型的委托(是Func<string>类型呢还是Func<object>?),所以上面代码在运行时会抛出ArgumetException(错误信息为——委托必须具有相同的类型)。我们可以稍微修改下上面代码来使其不出现运行时错误

Func
stringfunc = () => ""; // 转换 委托类型 Func
tempfunc = new Func(stringfunc); Func objectfunc = () => new object(); Func combined = tempfunc + objectfunc;

 

四、小结

   虽然可能这个系列对实际的开发中没有多大的帮助,但是我个人认为基础还是需要打扰,只有基础打好了,才可以让我们飞的更远,更容易掌握新的技术,所以我会一直坚持下去写完这个系列的, 希望对大家巩固基础知识有所帮助。(我觉得尤其是在校学生,应该更加注重基础知识的巩固,然后写一些例子来加深对基础知识的理解)。

 

转载于:https://www.cnblogs.com/unaestodo/p/3168696.html

你可能感兴趣的文章
centos7的php5.4竟然不支持原生的mysql
查看>>
使用IntelliJ IDEA开发SpringMVC网站(四)用户管理
查看>>
Maven依赖Scope标签用法
查看>>
堆排序的原理
查看>>
ajax加载数据到页面无法打印的解决办法
查看>>
js 验证中文
查看>>
MySQL给查询结果添加一表表示行号或名次(1)
查看>>
Linux下运行java DES AES加解密
查看>>
DataNode 运行状况
查看>>
牛津词典 2018 年度词汇 ——「有毒」!
查看>>
XIB的是用
查看>>
Learning Data Structure_2_线性表、栈和队列
查看>>
驱动外置+原版安装方式『XLOS_Windows8_Pro_X86纯净版_V1.0』
查看>>
Oracle创建表语句(Create table)语法详解及示例
查看>>
Java基础之Http协议的理解与总结
查看>>
Android Arcface人脸识别sdk使用工具类
查看>>
android studio单个工程文件的代理设置
查看>>
Agent admitted failure to sign using the key
查看>>
grep 应用
查看>>
我的友情链接
查看>>