内容简介:版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tkokof1/article/details/86627038
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tkokof1/article/details/86627038
本文简述了 C# 中协变和逆变的一些知识
在 C# 中, 协变 和 逆变 能够实现 数组类型 和 委托类型 的隐式引用转换, .NET Framework 4 (包括)以后, C# 也开始支持在 泛型接口 和 泛型委托 中使用协变和逆变,下面的内容也主要围绕泛型类型参数的协变和逆变来进行讲解.
- 什么是协变?
所谓协变(Covariance),是指能够使用比原始指定的类型派生程度更大的类型,简单理解就是 子类转为父类 这种变化.
C# 中协变对应的关键字为 out ,我们一起来看个例子:
// generics covariance delegate public delegate T CovarianceDelegate<out T>(); public static string Func() { return string.Empty; } // ... // CovarianceDelegate<string> can assign to CovarianceDelegate<object> CovarianceDelegate<string> d1 = Func; CovarianceDelegate<object> d2 = d1; object o = d2();
上面代码中的函数 Func , 正常应该对应于委托 CovarianceDelegate<string> ,但是因为我们使用了协变( <out T> ),所以类型参数间只要构成 子类(示例中是 string)转父类(示例中是 object) 关系时便可以正确进行隐式引用转换,所以示例中将 d1(CovarianceDelegate<string>) 赋值于 d2(CovarianceDelegate<object>) 是合法的.
另外注意一点的就是,协变( out )的泛型类型参数只能作为输出参数,不能作为输入参数,关键字 out 的字面意思也很好的说明了这一点,下面的代码便是一个误用的例子:
// error, T just can be output param ... public delegate T CovarianceDelegate<out T>(T input);
我们可以拿之前的示例来加深一下理解:
d1 是委托 CovarianceDelegate<string>,其返回一个 string 类型, d2 的委托 CovarianceDelegate<object>,其返回一个 object 类型, 我们将 d1 赋值给 d2, 并调用 d2 的话(object o = d2()),实际上而言, 内部返回的应该是一个 string 类型(d2 -> d1 -> Func, Func 的返回类型是 string), 但是由于 string 类型可以正确的转换为 object 类型, 所以通过调用 d2 返回一个 object 类型是安全的(由内部的 string 类型转换而来)
上面的说明也解释了为何协变类型参数只能作为输出参数的原因,因为只有这样才能保证类型安全,如果不加这个限制,将其用于输入参数,我们将面对需要将父类转为子类的尴尬境地,类型安全自然难以保证.
- 什么是逆变?
所谓逆变(Contravariance),是指能够使用比原始指定的类型派生程度更小的类型,简单理解就是 父类转为子类 这种变化.
C# 中逆变对应的关键字为 in , 我们同样先来看个示例:
// generics contravariance delegate public delegate void ContravarianceDelegate<in T>(T val); public static void Func(object val) { } // ... // ContravarianceDelegate<object> can assign to ContravarianceDelegate<string> ContravarianceDelegate<object> d1 = Func; ContravarianceDelegate<string> d2 = d1; d2(string.Empty);
与协变( out )相对的,逆变( in )的泛型类型参数只能用于输入参数,不能用于输出参数,我们同样用上面的示例来讲解一下:
d1 是委托 ContravarianceDelegate<object>,其接受一个 object 类型参数, d2 是委托 ContravarianceDelegate<string>,其接受一个 stirng 类型参数, 我们将 d1 赋值给 d2,并调用 d2 的话(d2(string.Empty)), 实际传入的参数是 string 类型, 但期望的参数是 object 类型(d2 -> d1 -> Func, Func 接受的参数类型是 object 类型), 但是由于 string 类型可以正确的转换为 object 类型, 所以通过调用 d2 传入一个 string 类型参数是安全的(string 类型内部会转换为 object 类型)
可以看到,虽然逆变是指 父类转为子类 这种看似不安全的类型变化(一般认为,子类转为父类总是安全的,父类转为子类则是不安全的),但这只是 形式上 的( ContravarianceDelegate<object> 转为 ContravarianceDelegate<string>,形式上看是进行了 object 类型到 string 类型的转换 ),内部而言,因为限制了输入参数的关系,实际进行的仍然是 子类转为父类 的过程,这也是保证逆变类型安全的前提, 这点上逆变和协变其实是一致的 .
小结:
- 协变和逆变用于隐式引用转换
- 协变的关键字为 out ,被其修饰的参数类型只能用于输出参数
- 逆变的关键字为 in ,被其修饰的参数类型只能用于输入参数
- 子类总是可以安全的转为父类 是保证协变和逆变类型安全的统一前提
考虑以下代码:
public delegate void Delegate1<in T>(); public delegate void Delegate2<in T>(Delegate1<T> d1);
按照之前逆变( in )仅能作为输入参数的说明,"似乎"上面的代码没有什么问题,但实际上这两行代码并不能通过编译,原因我们可以通过下面的代码来进行理解(示例代码的前提是 Delegate2 支持逆变):
public static void Func1(Delegate1<object> d1) { } public static void Func2() { } Delegate2<object> d1 = Func1; Delegate2<string> d2 = d1; d2(Func2);
d1 是委托 Delegate2<object>, 其接受一个 Delegate1<object> 类型的参数, d2 是委托 Delegate2<string>, 其接受一个 Delegate1<string> 类型的参数, 将 d1 赋值给 d2, 并调用 d2 的话(d2(Func2)), 实际传入的参数是 Delegate1<string> 类型, 但期望的参数是 Delegate1<object> 类型(d2 -> d1 -> Func1, Func1 接受的参数类型是 Delegate1<object> 类型), 所以 Delegate2 支持逆变(in)的前提就是 Delegate1<string> 可以正确的转换为 Delegate1<object>, 即 Delegate1 应该支持协变(out)!
通过将 Delegate1 改为支持协变,代码就可以编译通过了:
public delegate void Delegate1<out T>(); public delegate void Delegate2<in T>(Delegate1<T> d1);
进一步的小结:
- 协变逆变需要根据具体情况分析,不能简单的参照输入参数及输出参数原则
- 输入参数及输出参数原则是依据参数本身而言的,不适用于参数的包装类型
参考资料
以上所述就是小编给大家介绍的《编程小知识之协变和逆变》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Java中的逆变与协变
- 编程范式 —— 函数式编程入门
- 【go网络编程】-HTTP编程
- 【go网络编程】-Socket编程
- c++并发编程—分布式编程
- Scala面向对象编程之Trait高级编程技术实践-JVM生态编程语言实战
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。