在C#中,为什么单个强制转换同时执行拆箱和枚举转换?

时间:2012-07-13 14:04:10

标签: c# enums specifications boxing type-safety

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

  // 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更改为别的东西(可能是longshort,...),然后突然上面的单播代码会停止工作,这看起来很傻。

当然,枚举DayOfWeekDateTimeKind并不特别。这些可以是任何枚举类型,包括用户定义的类型。

有点相关:Why does unboxing enums yield odd results?(将int直接解包到枚举中)

此外:

好的,如此多的答案和评论都集中在如何对待“引擎盖”下的枚举。虽然这本身很有趣,但我想更多地关注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可能会将YellowInteger视为具有不同类型元数据(或其所谓的)的Int32,但任何人都可以保证.NET不会“混淆”{{1取消装箱后还有一个YellowInteger?那么在C#规范中我可以看到Int32是否会成功(调用我的显式运算符方法)?

2 个答案:

答案 0 :(得分:5)

使用时:

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

你实际上是拆箱然后再投射。

但在你的第二个案例中:

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

你根本不进行任何施法。您只需取消装箱值即可。 The default underlying type of the enumeration elements is int.

更新以引用真实问题

您在评论中提到的specification

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 blogged about it in 2005 (CLI spec mistake with unboxing and enums)

答案 1 :(得分:3)

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

由于类型相同,因此操作只是取消装箱int

您只能将值解包为实际类型,因此在取消装箱之前进行投射不起作用。

<强>更新

如果您创建了一些将int枚举相互转换的代码,您将看到生成的IL中没有强制转换操作。当您点击枚举并将其解包到另一种类型时,只有unbox.any操作:

  

转换在中指定的类型的盒装表示   对未装箱形式的指示。

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

更新2:

我已达到极限,能够在没有更深入研究的情况下解释这里发生的事情,但我发现了这个问题:

How is it that an enum derives from System.Enum and is an integer at the same time?

解释如何处理枚举可能会有一些方法。