c# - 在 C# 中,为什么单个转换可以同时执行拆箱和枚举转换?

标签 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 接口(interface)没有什么特别之处;如果您愿意,也可以使用 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 并不特殊。这些可以是任何枚举类型,包括用户定义的类型。

有些相关: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 不会“开箱时混淆了 YellowIntegerInt32?那么我可以在 C# 规范中的哪个位置查看 (int)box 是否会成功(调用我的显式运算符方法)?

最佳答案

当您使用时:

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

感觉之前一定有人注意到了,去 Google 搜索“unboxing conversion enum”然后猜猜是什么? Skeet blogged about it in 2005 (CLI spec mistake with unboxing and enums)

关于c# - 在 C# 中,为什么单个转换可以同时执行拆箱和枚举转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11472065/

相关文章:

ruby-on-rails - 如何使用 rspec 在路由规范中指定 https 协议(protocol)?

c# - 嵌套对象初始化语法

specifications - 如何编写网站规范

c# - 使用三元运算符返回 void 并获取参数的操作

c++ - 类似于枚举的 using 声明?

C# 锁和异步方法

java - 使用 JPA 和 Spring 时如何更改 java.lang.Enum 的序数?

c - 位域中的枚举 - ANSI C

c# - D 的作用域失败/成功/退出是否必要?

c# - MVC 使用 JQuery 更新模型属性