11

通常、最初に値型をボックス化解除し、次に何らかの値型変換を別の値型に実行するには、 2 つのキャストが必要であると予想し、期待します。これが当てはまる例を次に示します。

  // 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)

私のスマイリーからわかるように、キャストを 1 つだけ使用すると、これらの変換が失敗することを嬉しく思います。結局のところ、1 つの操作で値の型を別の値の型にボックス化解除しようとするのは、おそらくコーディングの誤りです。

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 バージョンで (仕様の変更なしに) 改善される可能性がある "不特定の動作" だと思いますか? それは重大な変更になります。

DayOfWeekまた、いずれかの列挙型 (またはDateTimeKind私の例では)のサプライヤが、列挙型のいずれかの基になる型を別のもの ( 、 、 ... のint可能性があります) に変更することを決定した場合、突然上記のワンキャスト コードは機能しなくなりますが、これはばかげているようです。longshort

もちろん、enumDayOfWeekDateTimeKindは特別なものではありません。これらは、ユーザー定義のものを含む、任意の列挙型である可能性があります。

やや関連:列挙型のボックス化を解除すると奇妙な結果が生じるのはなぜですか? (intを列挙型に直接アンボックスします)

添加:

OK、非常に多くの回答とコメントが、列挙型が「内部で」どのように扱われるかに焦点を当てています。これはそれ自体興味深いことですが、観察された動作が 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 が aを異なる型のメタデータ (またはそれが呼ばれるもの) を持つYellowInteger単なる an として扱う可能性があることです。では、C# 仕様のどこで成功するか (明示的な演算子メソッドを呼び出す) を確認できますか?Int32YellowIntegerInt32(int)box

4

2 に答える 2

5

使用する場合:

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

あなたは実際に箱を開けてからキャストしています。

しかし、あなたの2番目のケースでは:

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

誰かが以前に気付いたに違いない気がして、Google に行き、「unboxing conversion enum」を検索し、何を推測しましたか? 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 に答える