Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么协方差和逆变不支持值类型_C#_.net_C# 4.0_Covariance_Contravariance - Fatal编程技术网

C# 为什么协方差和逆变不支持值类型

C# 为什么协方差和逆变不支持值类型,c#,.net,c#-4.0,covariance,contravariance,C#,.net,C# 4.0,Covariance,Contravariance,IEnumerable是共变量,但它不支持值类型,只支持引用类型。以下简单代码已成功编译: IEnumerable<string> strList = new List<string>(); IEnumerable<object> objList = strList; 原因解释如下: 差异仅适用于引用类型;如果为变量类型参数指定值类型,则该类型参数对于生成的构造类型是不变的 我搜索并发现一些问题提到的原因是值类型和引用类型之间的装箱。但我仍然不太清楚为什么拳

IEnumerable
是共变量,但它不支持值类型,只支持引用类型。以下简单代码已成功编译:

IEnumerable<string> strList = new List<string>();
IEnumerable<object> objList = strList;
原因解释如下:

差异仅适用于引用类型;如果为变量类型参数指定值类型,则该类型参数对于生成的构造类型是不变的

我搜索并发现一些问题提到的原因是值类型和引用类型之间的装箱。但我仍然不太清楚为什么拳击是原因


有人能简单详细地解释一下为什么协方差和反方差不支持值类型,以及装箱如何影响值类型吗?

基本上,当CLR可以确保不需要对值进行任何代表性更改时,方差适用。所有引用看起来都一样-因此您可以使用
IEnumerable
作为
IEnumerable
而不改变表示形式;本机代码本身根本不需要知道您对这些值做了什么,只要基础设施保证它肯定是有效的

对于值类型,这不起作用-要将
IEnumerable
视为
IEnumerable
,使用序列的代码必须知道是否执行装箱转换

你可能想阅读Eric Lippert的文章,了解更多关于这个话题的信息

编辑:我自己重读了Eric的博客文章,这篇文章至少与身份和代表性同样重要,尽管两者是相互联系的。特别是:

这就是为什么接口和委托类型的协变和逆变转换要求所有不同类型的参数都是引用类型。为了确保变量引用转换始终保持身份,所有涉及类型参数的转换也必须保持身份。确保类型参数上的所有非平凡转换都保持身份的最简单方法是将它们限制为引用转换


我认为一切都是从LSP的定义开始的(Liskov替代原则),它的具体情况如下:

如果q(x)是关于T类型的对象x的可证明属性,那么对于S类型的对象y,q(y)应该为真,其中S是T的子类型

但是值类型,例如
int
不能替代
C
中的
object
。 证明很简单:

int myInt = new int();
object obj1 = myInt ;
object obj2 = myInt ;
return ReferenceEquals(obj1, obj2);

即使我们为对象指定了相同的“引用”,也会返回
false

如果您考虑底层的表示(即使这实际上是一个实现细节),可能更容易理解。以下是字符串的集合:

IEnumerable<string> strings = new[] { "A", "B", "C" };
它是三个元素的集合,每个元素都是对字符串的引用。您可以将其强制转换为对象集合:

IEnumerable<object> objects = (IEnumerable<object>) strings;
[0]:int=1 [1] :int=2 [2] :int=3 要将其转换为
IEnumerable
,必须通过装箱INT来转换数据:

[0] : object reference -> 1 [1] : object reference -> 2 [2] : object reference -> 3 [0]:对象引用->1 [1] :对象引用->2 [2] :对象引用->3
此转换需要的不仅仅是转换。

它归结为一个实现细节:值类型的实现与引用类型不同

如果强制将值类型视为引用类型(例如,通过接口引用值类型,将其框起来),则可以获得差异

查看差异的最简单方法是简单地考虑<代码>数组< /> >:一个值类型数组被连续地(直接)放在内存中,其中引用类型的数组只在内存中连续地具有引用(指针);指向的对象是单独分配的

另一个(相关的)问题(*)是(几乎)所有引用类型都具有相同的表示形式以用于差异,许多代码不需要知道类型之间的差异,因此可以实现协变和反变(并且很容易实现——通常只需省略额外的类型检查)


(*)这可能被认为是同一个问题……

@CuongLe:从某种意义上说,这是一个实现细节,但我相信这是限制的根本原因。@AndréCaron:Eric的博客文章在这里很重要——它不仅是代表性,而且是身份保护。但表示保留意味着生成的代码根本不需要考虑这一点。确切地说,标识不能被保留,因为
int
不是
对象的子类型。事实上,一个代表性的变化是必需的,这只是一个结果。为什么int不是object的子类型?Int32继承自System.ValueType,后者继承自System.Object。@DavidKlempfner我认为@AndréCaron comment措词不当。任何值类型,如
Int32
都有两种表示形式,“已装箱”和“未装箱”。编译器必须插入代码才能从一种形式转换为另一种形式,即使这在源代码级别通常是不可见的。实际上,底层系统只将“装箱”形式视为
对象的子类型
,但是,每当一个值类型被分配给兼容接口或类型为
object
的对象时,编译器就会自动处理这个问题。我认为您使用的原则是正确的,但没有证据证明:
int
不是
object
的子类型,因此该原则不适用。您的“证明”依赖于中间表示法
Integer
,它是
object
的子类型,并且语言对其进行隐式转换(
object obj1=myInt;
实际扩展为
object obj1=new Integer(myInt)
)。该语言负责类型之间的正确转换,但是int的行为与我们期望从对象的子类型中得到的行为并不一致。我的全部观点就是,
int
不是
object
的子类型。
IEnumerable<object> objects = (IEnumerable<object>) strings;
[0] : object reference -> "A" [1] : object reference -> "B" [2] : object reference -> "C"
IEnumerable<int> ints = new[] { 1, 2, 3 };
[0] : int = 1 [1] : int = 2 [2] : int = 3 [0] : object reference -> 1 [1] : object reference -> 2 [2] : object reference -> 3