【问题标题】:Confused about boxing, casting, implicit etc对拳击,铸造,隐式等感到困惑
【发布时间】:2011-01-22 17:32:49
【问题描述】:

从书中:

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[] 不能隐式转换。什么??

【问题讨论】:

  • +1 你的问题让我对我不自然地做的事情有所了解,但现在我有一个无问题的答案。 =)

标签: c#


【解决方案1】:

不是要重述这个问题,而是因为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[] 是合法的,那么我现在突然(神奇地)在我的int[] 数组中有一个string 值——这是永远不应该发生的事情,实际上可以' t 发生,因为 int 变量不能保存 string 值。

其他人(正确地)指出像这样的东西确实编译:

string[] strings = new string[] { "a", "b", "c" };
Object[] objects = strings;

objects[1] = 4;

我会把它留给像 Eric Lippert 这样的人来解释为什么这种操作有效(它本质上是假设协方差,但不一定如此)[编辑: 感谢Tim Goodman,实际上发布了 Eric 对此的解释或至少声明],但从根本上说,任何引用类型技术上都能够持有对任何类型的引用。换句话说,当我声明string 变量时,它分配的内存量(用于变量)与我要声明DbConnection 变量一样;它们都是引用类型。对于值类型,分配的内存量取决于类型,它们从根本上是不兼容的。

但是,您会注意到,在执行最后一步(将 int 分配给第二个数组元素)时,您将收到 runtime 异常 (ArrayTypeMismatchException),因为底层数组实际上是string[]

【讨论】:

  • 我不确定这是否正确 - object[] o = new string[] { "a", "b", "c" } 在 C# 中是合法的,所以它可能是由于 int是一个值类型。
  • 仅仅因为 .NET 4 中的协方差支持没有解决问题,并不意味着它不是协方差问题。由于您提出的问题,这是一个永远无法支持的协方差问题。
  • @Lee:你说得对。我已经调整了答案以反映这一点。
  • +1 很好的解释!我从未尝试过: Object[] objs = new int[] {...} 对我来说,我不希望这种情况发生似乎是合乎逻辑的,但有时这种行为或神奇的想法值得一个很好的解释。你给出这个解释。谢谢!
  • 感谢您的认可,亚当。我找到了 Eric 的第二句话(现在添加到我的答案中),它解释了更多。
【解决方案2】:

显然这是一个令人困惑的问题。不幸的是,蒂姆·古德曼的回答,我认为这是最清楚、最正确地解决了实际提出的问题的回答,被否决并删除了。李的回答也很到位。

为了总结所有这些,让我将问题改写为一些更精确的问题。

什么是适用于分配兼容性的“协方差”?

简而言之:考虑从一种类型到另一种类型的映射。比如说,int --> int[]string --> string[] 等等。也就是说,映射“x 映射到 x 数组”。如果该映射保留分配兼容性,则它是协变映射。简而言之,我们说“数组是协变的”,意思是“数组赋值兼容规则与其元素类型的赋值兼容规则相同,因为元素类型到数组类型的映射是协变的”。

有关协方差和赋值兼容性之间关系的更多信息,请参阅:

Difference between Covariance & Contra-variance

http://blogs.msdn.com/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx

C# 中的数组实际上是协变的吗?

只有当元素类型是引用类型时它们才是协变的。

这种协方差类型安全吗?

没有。它被打破。它缺乏类型安全意味着在编译时成功的操作可能会在运行时抛出异常。这也意味着每个可能导致此类异常的数组赋值都需要检查以查看是否应该抛出异常,这是昂贵的。基本上,我们在这里拥有的是一个危险的、昂贵的功能,您不能选择退出。我对此并不特别高兴,但这就是我们所坚持的。

请参阅http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx,了解有关这种损坏的协方差形式的更多详细信息。

CLR 是否支持值类型数组的协方差?

是的。见

http://blogs.msdn.com/ericlippert/archive/2009/09/24/why-is-covariance-of-value-typed-arrays-inconsistent.aspx

我从我的回答中写到:

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 将它们视为对象,而不是专门的字符串。你只需看看字符串 fun 和它的一个对象。

当您将 int 转换为对象时,我们会分配一个盒子来放入 int。 int 是一个 32 位整数,包含它自己的值。该框表示为 GC 堆中的 32 位或 64 位托管地址,然后包含 32 位 int 值。这些是完全不同的位!将对象转换为 int 需要大量工作。你不能只看 int 很有趣,嘿,它看起来像一个对象。您必须分配内存才能使其正常工作。

这就是为什么不能将 int 数组转换为对象数组的原因。一个由十个整数组成的数组可以占用 320 位,每个位都是整数本身的一部分。一个由 10 个对象组成的数组是 320 位或 640 位 GC 堆上的托管地址,谁来做所有这些分配?我们希望参考转换快速便宜;这种转换需要我们基本上分配一个全新的数组并复制整个内容;结果将不再具有与原始数组的引用身份,因此对它的更改将丢失。或者,我们必须编写更多代码来对新数组进行更改并将它们编组回旧数组。

http://ericlippert.com/2009/03/03/representation-and-identity/

有关保留表示的转换的更多讨论。

这是否回答了您的问题?这是一个令人困惑的话题,我知道。它深入探讨了 CLR 的基本设计决策。

【讨论】:

  • 谢谢,埃里克。由于您的积极评价,我已取消删除原来的答案。
【解决方案3】:

这是因为 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[] 的赋值兼容”。

编辑

另请参阅我的其他答案,我在发布此答案之前已将其删除,但此后未删除。

【讨论】:

  • 清除事物。我想我的问题是我没有真正理解数组也是一个“类型”(引用?),它是从 Object 派生的,也没有理解 Object 和 Object[] 之间的区别
  • 是的,数组是对象这一事实是 C# 和 C++ 之间的区别之一
【解决方案4】:

编辑:由于 Eric Lippert 的积极评论,取消删除此内容(尽管有反对票)。

很多答案都提到 int[] 不能转换为 object[] 。 . .这当然是正确的,但这并没有真正解决为什么会出现这种情况的问题。不是说语言设计者的话,但我怀疑这是因为将 int[] 转换为 object[] 的过程需要分配一个全新的数组并将 int 数组的每个元素装箱为对象数组...这是一个代价高昂的操作,可能不是程序员想要的。您更有可能希望将整个 int[] 装箱到单个对象中。

【讨论】:

  • 你的推理不正确,为什么你不能在 int[] 和 object[] 之间转换。这是因为它不是两种类型之间的协变关系。
  • @Keith:你是在循环推理。 (1) 为什么int[]到object[]的转换是非法的?因为值类型数组转换不是协变的。 (2) 为什么它们不是协变的?因为协变意味着从 T 到 U 的合法转换意味着从 T[] 到 U[] 的合法转换,但没有从 int[] 到 object[] 的这种转换,因此它不是协变的。我们以(2)为前提证明了(1),但我们以(1)为前提证明了(2)。这就是循环推理。 Tim 实际上给出了设计选择的动机,在这种情况下不允许协方差。
【解决方案5】:

在 C# 中以下是合法的:

object[] objects = new string[] { "a", "b", "c" };

所以你可能会期待

object[] ints = new int[] { 1, 2, 3}

也是有效的,因为与字符串一样,int 可以隐式转换为对象。但是,在第一种情况下,string[] 包含一系列字符串引用,它们也是有效的对象引用。在第二种情况下,int[] 包含一个 int values 序列,并且这些不是有效的对象引用。现在,您可以将 int 分配给对象,如示例所示,但这会导致创建新对象(装箱)。因此,要使其有效:

object[] ints = new int[] { 1, 2, 3}

需要创建一个 new 数组,而第一个数组只是一个参考副本。因此,解决方法是显式创建一个新数组:

object[] ints = (new int[] { 1, 2, 3}).Select(i => (object)i).ToArray();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-10
    • 2013-09-06
    • 1970-01-01
    相关资源
    最近更新 更多