11

通常,人们会期望并希望,首先需要两次强制转换才能将值类型拆箱,然后执行某种值类型转换为另一种值类型。这是一个例子:

  // create boxed int
  IFormattable box = 42;       // box.GetType() == typeof(int)


  // unbox and narrow
  short x1 = (short)box;       // fails runtime :-)
  short x2 = (short)(int)box;  // OK

  // unbox and make unsigned
  uint y1 = (uint)box;         // fails runtime :-)
  uint y2 = (uint)(int)box;    // OK

  // unbox and widen
  long z1 = (long)box;         // fails runtime :-)
  long z2 = (long)(int)box;    // OK (cast to long could be made implicit)

正如您从我的笑脸中看到的那样,如果我只使用一个演员表,我很高兴这些转换将失败。毕竟,尝试在一次操作中将值类型拆箱为不同的值类型可能是一个编码错误。

IFormattable(界面没有什么特别之处;object如果您愿意,也可以使用该类。)

但是,今天我意识到这与枚举不同(当(并且仅当)枚举具有相同的基础类型时)。这是一个例子:

  // create boxed DayOfWeek
  IFormattable box = DayOfWeek.Monday;    // box.GetType() == typeof(DayOfWeek)


  // unbox and convert to other
  // enum type in one cast
  DateTimeKind dtk = (DateTimeKind)box;   // succeeds runtime :-(

  Console.WriteLine(box);  // writes Monday
  Console.WriteLine(dtk);  // writes Utc

我认为这种行为是不幸的。真的应该说是必须的(DateTimeKind)(DayOfWeek)box。阅读 C# 规范,我看不出数字转换和枚举转换之间存在这种差异的理由。在这种情况下,感觉就像类型安全丢失了。

您是否认为这是“未指定的行为”,可以在未来的 .NET 版本中改进(无需更改规范)?这将是一个突破性的变化。

此外,如果任一枚举类型(DayOfWeekDateTimeKind在我的示例中)的供应商决定将其中一个枚举类型的基础类型从其他类型更改为int其他类型(可能是long, short, ...),那么突然之间上面的一次性代码将停止工作,这看起来很愚蠢。

当然,枚举DayOfWeekDateTimeKind没有什么特别之处。这些可以是任何枚举类型,包括用户定义的类型。

有点相关:为什么拆箱枚举会产生奇怪的结果?(将 anint直接拆箱到枚举中)

添加:

好的,很多答案和评论都集中在如何“在幕后”处理枚举。虽然这本身很有趣,但我想更多地关注观察到的行为是否包含在 C# 规范中。

假设我写了类型:

struct YellowInteger
{
  public readonly int Value;

  public YellowInteger(int value)
  {
    Value = value;
  }

  // Clearly a yellow integer is completely different
  // from an integer without any particular color,
  // so it is important that this conversion is
  // explicit
  public static explicit operator int(YellowInteger yi)
  {
    return yi.Value;
  }
}

然后说:

object box = new YellowInteger(1);
int x = (int)box;

那么,C# 规范是否说明这是否会在运行时成功?就我而言,.NET 可能会将 aYellowInteger视为具有不同类型元数据(或其他任何名称)的 a,但是任何人都可以保证 .NET在拆箱时Int32不会“混淆” aYellowInteger和 a吗?Int32那么我可以在 C# 规范中的哪个位置查看是否(int)box会成功(调用我的显式运算符方法)?

4

2 回答 2

5

当您使用:

IFormattable box = 42; 
long z2 = (long)(int)box;

你实际上是在拆箱然后铸造。

但在你的第二种情况下:

IFormattable box = DayOfWeek.Monday; 
DateTimeKind dtk = (DateTimeKind)box;

你根本不执行任何铸造。您只需将值拆箱即可。枚举元素的默认基础类型是 int。

更新以引用真正的问题

您在评论中提到的规范:

The explicit enumeration conversions are:
...
From any enum-type to any other enum-type.

这实际上是正确的。我们不能隐式转换:

//doesn't compile
DateTimeKind dtk = DayOfWeek.Monday;

但我们可以显式转换:

DateTimeKind dtk = (DateTimeKind)DayOfWeek.Monday;

您似乎找到了仍然需要这样做的情况。但与拆箱结合使用时,只需指定显式转换即可省略拆箱

更新 2

有一种感觉,之前一定有人注意到了,去谷歌搜索“取消装箱转换枚举”,你猜怎么着?Skeet 在 2005 年发表了关于它的博客(CLI 规范错误与拆箱和枚举)

于 2012-07-13T14:09:50.497 回答
3

这是因为它们实际上在运行时被表示为它们的底层值类型。它们都是int,这与您的失败案例的情况相同 - 如果您在这种情况下更改枚举类型,这也会失败。

由于类型相同,因此操作只是将int.

您只能将值拆箱为它们的实际类型,因此在拆箱之前进行强制转换不起作用。

更新

如果您创建一些将int枚举相互转换的代码,您将看到生成的 IL 中没有转换操作。当您将枚举装箱并将其拆箱为另一种类型时,只有以下unbox.any操作:

将指令中指定的类型的装箱表示转换为其未装箱形式。

在这种情况下,它是每个枚举,但它们都是int.

更新 2:

我已经达到了能够解释这里发生的事情的极限,而无需我进行更深入的研究,但我发现了这个问题:

枚举如何从 System.Enum 派生并且同时是整数?

它可能有助于解释如何处理枚举。

于 2012-07-13T14:09:06.777 回答