编程小知识之协变和逆变

栏目: ASP.NET · 发布时间: 5年前

内容简介:版权声明:本文为博主原创文章,未经博主允许不得转载。 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);

进一步的小结:

  • 协变逆变需要根据具体情况分析,不能简单的参照输入参数及输出参数原则
  • 输入参数及输出参数原则是依据参数本身而言的,不适用于参数的包装类型

参考资料

  1. 协变和逆变 (C#)
  2. 泛型中的协变和逆变
  3. 深入理解 C# 协变和逆变
  4. 理解 C# 泛型接口中的协变与逆变

以上所述就是小编给大家介绍的《编程小知识之协变和逆变》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

独角兽之路

独角兽之路

三节课产品社区 / 电子工业出版社 / 2016-7 / 79.00元

对2~3年以上经验的互联网人来说,最好的学习可能不是听课,而是分析各类真实的产品和运营案例。而《独角兽之路:20款快速爆发且极具潜力的互联网产品深度剖析(全彩)》正好提供了对滴滴出行、百度外卖、懂球帝、快手App等20款极具代表性的准独角兽产品的发展路径的深度分析。 通过阅读《独角兽之路:20款快速爆发且极具潜力的互联网产品深度剖析(全彩)》,你可以发现互联网产品发展的背后,或许存在着某些共......一起来看看 《独角兽之路》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具