从书中可以看出:
1) int i = 7;
2) Object o = i; // Implicit boxing int-->Object
3) Object[] a3 = new int[] { 1, 2 }; // Illegal: no array conversion
3)中的任务是非法的 因为int不是引用类型 所以int []不是含蓄的 可转换为Object []
我不懂。在第2行)它显示int可以隐式转换为Object,而在第三行中,它表示int []不可隐式转换。哇??
答案 0 :(得分:22)
不重述问题,但这是因为int[]
不能隐式(或明确地)转换为Object[]
。
数组本身就是类型。 int[]
只是表示“一系列int
变量”,就像Object[]
表示“一系列Object
变量”一样。
顺便提一下,所有数组都是引用类型,只是int[]
是表示一系列特定值类型的引用类型。
修改强>
不要让关于协方差的讨论混淆你。这是一个被广泛误解的话题,对于初学者试图解决的问题并不是真正有益的话题。是的,.NET 4.0引入了将操作指定为协变或逆变的功能,但不允许在Object[]
和int[]
等不兼容类型之间进行赋值兼容。请考虑以下示例,假设它将进行编译。
int[] ints = new int[] { 1, 2, 3 };
Object[] objects = ints;
objects[1] = "hello";
int foo = ints[1];
现在我们遇到了问题。如果将int[]
分配给Object[]
是合法的,那么我现在突然(神奇地)在我的string
数组中有一个int[]
值 - 这应该永远不会发生,实际上无法发生,因为int
变量无法保存string
值。
其他人(正确地)指出像这样的东西编译:
string[] strings = new string[] { "a", "b", "c" };
Object[] objects = strings;
objects[1] = 4;
我会把它留给像Eric Lippert这样的人来解释为什么这种操作有效(它基本上假定协方差时不一定是这种情况)[编辑: 感谢蒂姆·古德曼,实际上发表了关于这个的Eric的解释,或者至少是声明,但从根本上说,任何参考类型都技术上能够持有对任何类型的引用。换句话说,当我声明一个string
变量时,它会分配相同数量的内存(对于变量),就像我要声明一个DbConnection
变量一样;他们都是参考类型。对于值类型,分配的内存量取决于类型,它们从根本上是不兼容的。
但是,您将注意到,在执行最后一步(将ArrayTypeMismatchException
分配给第二个数组元素)时,您将获得运行时异常(int
),因为底层数组实际上是string[]
。
答案 1 :(得分:13)
显然这是一个令人困惑的问题。不幸的是,蒂姆古德曼的答案,我认为是最明确和正确地解决实际陈述问题的答案,被低估并删除。李的答案也很好地打破了这个标志。
总结一下,让我把问题重新解释为一些更精确的问题。
什么是“协方差”,因为它适用于分配兼容性?
简要说明:考虑从一种类型到另一种类型的映射。比方说,int --> int[]
,string --> string[]
等等。也就是说,映射“x映射到x的数组”。如果该映射保留赋值兼容性,则它是协变映射。简而言之,我们说“数组是协变的”,意思是“数组赋值兼容性规则与其元素类型的赋值兼容性规则相同,因为从元素类型到数组类型的映射是协变的”。
有关协方差和赋值兼容性之间关系的更多信息,请参阅:
Difference between Covariance & Contra-variance
数组在C#中是否实际协变?
仅当元素类型是引用类型时,它们才是协变的。
这种协方型是否安全?
没有。它被打破。它缺乏类型安全性意味着在编译时成功的操作可能会在运行时抛出异常。这也意味着对可能导致此类异常的数组的每个赋值都需要检查以查看是否应该抛出异常,这很昂贵。基本上我们这里有一个危险的,昂贵的功能,你不能选择退出。我对此并不特别高兴,但这就是我们所坚持的。
有关这种破坏的协方差形式的详细信息,请参阅http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx。
CLR是否支持值类型数组的协方差?
是。见
我从答案中写道:
Why does my C# array lose type sign information when cast to object?
C#是否支持安全的协方差形式?
从C#4开始,是的。我们支持使用引用类型参数化的通用接口和委托的协方差和逆变。 C#3不支持通用差异。
例如,在C#4中,可以将实现IEnumerable<string>
的对象分配给IEnumerable<object>
类型的变量。
请参阅http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx
有关此功能的扩展讨论。
现在我们已经完成了预备工作,我们可以提出您的实际问题:
为什么泛型和数组协方差只适用于引用类型,而不适用于值类型?
因为某些转换是表示保留的,有些是表示更改。
当你说
时string x = "hello";
object y = x;
存储y的内容与存储x的内容完全相同。它们都是位模式,意味着“在GC堆上引用此对象”。无论CLR是将这些位解释为对象的引用还是对字符串的引用,该位模式都是相同的。
数组只不过是一大堆存储。当您重新解释string []的内容作为对象的引用时,其位中的任何内容都不得更改。它们保持完全相同,CLR认为它们是对象,而不是特定的字符串。你只要看看有趣的字符串及其对象。
将int转换为对象时,我们会分配一个框来放入int。 int是一个32位整数,包含自己的值。该框表示为GC堆中的32或64位管理地址,然后包含32位int值。那些是完全不同的位!将对象转换为int需要大量的工作。你不能只看看有趣的嘿,它看起来像一个对象。您必须分配内存才能使其正常工作。
这就是为什么你不能将int数组转换为对象数组的原因。十个整数的数组可能占用320位,每个位都是整数本身的一部分。十个对象的数组是GC堆上的320或640位托管地址,谁将完成所有这些分配?我们希望参考转化快和便宜;这种转换要求我们基本上分配一个完整的新数组并复制整个内容;结果将不再具有与原始数组的引用标识,因此对它的更改将丢失。或者,我们必须编写更多代码,这些代码对新数组进行了更改,并将它们编组回原来的数组。
见
http://ericlippert.com/2009/03/03/representation-and-identity/
有关表示保留转换的更多讨论。
这会回答你的问题吗?我知道这是一个令人困惑的话题。它深入研究了CLR的基本设计决策。
答案 2 :(得分:8)
这是因为int是值类型。这样的转换 对于引用类型来说是合法的。
根据C#编译器团队的Eric Lippert:
C#的数组协方差规则是“如果X是隐式可转换为引用类型Y的引用类型,则X []可隐式转换为Y []”
提问者问为什么这与int可转换为object的事实形成对比。请注意,int继承自object,但int []不继承自object []。而int []和object []都继承自System.Array
修改强>
我找到了more from Eric为什么这种转换被允许用于引用类型。
它被添加到CLR中,因为Java需要它,而CLR设计者希望能够支持类似Java的语言。然后我们将它添加到C#,因为它在CLR中。这个决定在当时颇具争议,我对此并不十分满意,但现在我们无能为力。
但是请注意,C#的数组协方差规则实际上与CLR的规则略有不同,如here所述。简而言之,CLR的规则是“如果X与Y兼容,那么X []赋值与Y []”兼容。
修改强>
另请参阅我的其他答案,我在发布此答案之前已将其删除,但此后已取消删除。
答案 3 :(得分:4)
很多答案都提到int []不能转换为object []。 。 。这当然是正确的,但这并没有真正解决为什么的问题。将语言放入语言设计者的口中并不意味着,但我怀疑这是因为将int []转换为object []的过程需要分配一个全新的数组并将int数组的每个元素装入一个元素中。对象数组...这是一个代价高昂的操作,可能不是程序员想要的。您更希望将整个int []封装到单个对象中。
答案 4 :(得分:3)
在C#中,以下是合法的:
object[] objects = new string[] { "a", "b", "c" };
所以你可以期待
object[] ints = new int[] { 1, 2, 3}
也有效,因为像string一样,int可以隐式转换为object。但是,在第一个实例中,string[]
包含一系列字符串引用,这些引用也是有效的对象引用。在第二种情况下,int[]
包含一系列int 值,并且这些不是有效的对象引用。现在,您可以在示例中显示为对象分配int,但这会导致创建新对象(装箱)。因此,为了这个有效:
object[] ints = new int[] { 1, 2, 3}
需要创建 new 数组,而第一个只需要一个引用副本。因此,解决方案是显式创建一个新数组:
object[] ints = (new int[] { 1, 2, 3}).Select(i => (object)i).ToArray();