EnumがSystem.Enumから派生し、同時に整数であることはどうですか?
-
30-09-2019 - |
質問
編集: :下のコメント。また、 これ.
これが私を混乱させているものです。私の理解は、私がこのような列挙を持っているなら...
enum Animal
{
Dog,
Cat
}
...私が本質的にやったことは定義されています 値タイプ 呼び出されました Animal
2つの定義された値で、 Dog
と Cat
. 。このタイプはから派生します 参照タイプ System.Enum
(型を大切にすることは通常はできません - 少なくともC#ではそうではありませんが、この場合は許可されています)。 int
値。
上記の列挙タイプを説明したばかりの方法が真実だった場合、次のコードがスローすることを期待します InvalidCastException
:
public class Program
{
public static void Main(string[] args)
{
// Box it.
object animal = Animal.Dog;
// Unbox it. How are these both successful?
int i = (int)animal;
Enum e = (Enum)animal;
// Prints "0".
Console.WriteLine(i);
// Prints "Dog".
Console.WriteLine(e);
}
}
通常は、 値のタイプをから解除することはできません System.Object
正確なタイプ以外のものとして. では、上記はどのように可能ですか?それはまるでです Animal
タイプ は an int
(だけでなく コンバーチブル に int
) と は an Enum
(だけでなく コンバーチブル に Enum
) 同時に。 複数の継承ですか?します System.Enum
どういうわけか継承 System.Int32
(私が不可能になるとは思っていなかったもの)?
編集: :上記のいずれかではありません。次のコードは、これを最終的に(私が思う)を示しています。
object animal = Animal.Dog;
Console.WriteLine(animal is Enum);
Console.WriteLine(animal is int);
上記の出力:
True False
両方 列挙に関するMSDNドキュメント C#仕様は「基礎となるタイプ」という用語を使用します。しかし、私はこれが何を意味するのか分かりませんし、それが列挙以外に言及して使用されていると聞いたこともありません。実際には「基礎となるタイプ」は何ですか 平均?
だから、これはさらに別です CLRから特別な治療を受けるケース?
私のお金はそうだった...しかし、答え/説明はいいでしょう。
アップデート: damien_the_unbeliever この質問に真に答えるための参照を提供しました。説明は、CLI仕様のパーティションIIにあります。
バインディングの目的(例:それを呼び出すために使用されるメソッド参照からメソッド定義を見つける場合)は、領域の基礎となるタイプとは異なります。コードの検証と実行など、他のすべての目的のために、 ボックス化されていない酵素は、その基礎となるタイプと自由に相互変換されます. 。酵素は、対応するボックス付きインスタンスタイプにボックス化できますが、このタイプは いいえ 基礎となるタイプの箱入りタイプと同じなので、ボクシングは元のタイプの列挙を失いません。
編集(もう一度??!): :待って、実際、私はそれを初めて読んだことを知りません。たぶん、それは特殊なアンボクシングの動作自体を100%説明していないかもしれません(ただし、この問題について多くの光を当てたので、私は受け入れられているようにダミアンの答えを残しています)。私はこれを調べ続けます...
別の編集: :男、それから Yodaj007の答え 別のループのために私を投げました。どういうわけか、列挙は int
;それでもです int
enum変数に割り当てることができます キャストなし? buh?
これはすべて最終的に照らされていると思います ハンスの答え, 、それが私がそれを受け入れた理由です。 (ごめんなさい、ダミアン!)
解決
はい、特別な治療。 JITコンパイラは、箱入りのバリュータイプの仕組みを強く認識しています。これは一般的に、バリュータイプが少し統合失調症に作用するものです。ボクシングには、参照タイプの値とまったく同じように動作するSystem.Object値を作成することが含まれます。その時点で、値タイプの値は、実行時に値のように動作しなくなります。たとえば、toString()のような仮想メソッドを持つことが可能になります。箱入りオブジェクトには、参照タイプと同様に、メソッドテーブルポインターがあります。
JITコンパイラは、intやbool up frongなどの値タイプのメソッドテーブルポインターを把握しています。彼らのボクシングとボクシングはです 非常に 効率的には、ほんの一握りのマシンコード命令が必要です。これは、競争力を高めるために.NET 1.0で効率的である必要がありました。 a 非常に その重要な部分は、値タイプの値が同じタイプに対してのみボックス化できないという制限です。これにより、ジッターが正しい変換コードを呼び出す大規模なスイッチステートメントを生成する必要がなくなります。それがしなければならないのは、オブジェクトのメソッドテーブルポインターを確認し、それが期待されるタイプであることを確認することです。オブジェクトから値を直接コピーします。おそらく注目すべきは、この制限がVB.NETに存在しないことです。そのCTYPE()演算子は、実際にこの大きなスイッチステートメントを含むヘルパー関数にコードを生成します。
Enumタイプの問題は、これが機能しないことです。 Enumsは、getunderlyingType()タイプを異なる場合があります。言い換えれば、ボックス化されていない値のサイズは異なるため、箱入りオブジェクトから値を単純にコピーするだけでは機能しません。ジッターはボックス化コードをインラインではなく、CLRのヘルパー関数への呼び出しを生成しなくなりました。
そのヘルパーの名前はjit_unbox()という名前で、SSCLI20ソースであるCLR/SRC/VM/Jithelpers.cppでそのソースコードを見つけることができます。列挙タイプを特に扱っているのがわかります。それは許容され、ある列挙型から別の列挙型へのボックス化を解除することができます。ただし、基礎となるタイプが同じである場合にのみ、そうでない場合は無効なCaStExceptionが表示されます。
これは、列挙がクラスとして宣言される理由でもあります。これは 論理 動作は参照タイプであり、導出された列挙タイプは次々にキャストできます。上記のタイプの互換性に関する上記の注目された制限があります。ただし、列挙型の値には、値タイプ値の動作が非常に大きくなります。セマンティクスとボクシングの動作をコピーしています。
他のヒント
EnumsはCLRによって特別に扱われます。 Goryの詳細に進みたい場合は、ダウンロードできます MSパーティションII 仕様。その中に、あなたはその酵素を見つける:
酵素は、他の価値タイプのものを超えた追加の制限に従います。酵素には、メンバーとしてフィールドのみが含まれているものとします(タイプの初期化装置またはインスタンスコンストラクターを定義してはなりません)。インターフェイスを実装してはなりません。それらは自動フィールドレイアウト(§10.1.2)を持っているものとします。それらは正確に1つのインスタンスフィールドを持っているものとし、それは基礎となるタイプの列挙でなければなりません。他のすべてのフィールドは静的で文字通りでなければなりません(§16.1)。
それが彼らがSystem.Enumから継承する方法ですが、「根本的な」タイプを持っている - それは彼らが許可されている単一のインスタンスフィールドです。
ボクシングの動作についても議論がありますが、基礎となるタイプへの明示的に解き放たれたことは説明していません。
パーティションI、8.5.2は、酵素は「既存のタイプの代替名」であるが、「[f]または一致する署名の目的であると述べています。
パーティションII、14.3の説明:「コードの検証と実行を含む他のすべての目的のために、その基礎となるタイプと自由にコンセントを自由に相互に相互に相互変換することができます。酵素は対応するボックス付きインスタンスタイプにボックス化できますが、このタイプは箱入りと同じではありません。基礎となるタイプのタイプなので、ボクシングは元のタイプの列挙を失いません。」
パーティションIII、4.32は、ボックス化の動作を説明しています。 値タイプ 内部に含まれています OBJ 割り当て互換性がある必要があります ValueType. 。 [注:これは、列挙タイプで動作に影響します。パーティションII.14.3を参照してください。エンドノート]」
私がここで注目しているのは、38ページからです ECMA-335 (私はあなたがそれを持っているためだけにそれをダウンロードすることをお勧めします):
CTSは、既存のタイプの代替名である列挙(列挙タイプとも呼ばれる)をサポートします。一致する署名の目的のために、列挙は基礎となるタイプと同じではありません。ただし、列挙のインスタンスは、基礎となるタイプに割り当て可能であり、その逆も同様です。つまり、キャスト(§8.3.3を参照)または強制(§8.3.2を参照)は、列挙から基礎となるタイプに変換するために必要ではなく、基礎となるタイプから列挙型にも必要ではありません。列挙は、次のように、真のタイプよりもかなり制限されています。
基礎となるタイプは、組み込みの整数型でなければなりません。 EnumsはSystem.Enumに由来するため、値タイプです。すべての値タイプと同様に、それらは封印されます(§8.9.9を参照)。
enum Foo { Bar = 1 }
Foo x = Foo.Bar;
このステートメントは、2番目の文のために偽りになります。
x is int
彼ら それは 同じ(エイリアス)ですが、署名は同じではありません。 Anとの変換 int
キャストではありません。
46ページから:
基礎となるタイプ - CTSの列挙は、既存のタイプ(§8.5.2)の代替名であり、その基礎となるタイプと呼ばれます。署名マッチング(§8.5.2)を除き、列挙は基礎となるタイプとして扱われます。このサブセットは、列挙が削除されたストレージタイプのセットです。
以前に私のfoo enumに戻ります。この声明は機能します:
Foo x = (Foo)5;
リフレクターの私の主な方法の生成されたILコードを検査する場合:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 1
.locals init (
[0] valuetype ConsoleTesting.Foo x)
L_0000: nop
L_0001: ldc.i4.5
L_0002: stloc.0
L_0003: call string [mscorlib]System.Console::ReadLine()
L_0008: pop
L_0009: ret
}
キャストがないことに注意してください。 ldc
86ページにあります。定数をロードします。 i4
151ページにあり、このタイプが32ビット整数であることを示しています。キャストはありません!
抽出された MSDN:
列挙要素のデフォルトの基礎となるタイプはintです。デフォルトでは、最初の列挙器には値0があり、各連続する列挙器の値は1増加します。
だから、キャストは可能ですが、あなたはそれを強制する必要があります:
基礎となるタイプは、各列挙器に割り当てられるストレージの量を指定します。ただし、列挙タイプから積分タイプに変換するには、明示的なキャストが必要です。
列挙にボックスすると object
, 、動物オブジェクトは由来します System.Enum
(実際のタイプは実行時に知られている)ので、実際には int
, 、したがって、キャストは有効です。
(animal is Enum)
戻り値true
: :このため、動物を列挙またはイベントに解除して、明示的なキャスティングを行うINTになります。(animal is int)
戻り値false
: :is
オペレーター(一般的なタイプチェック)は、酵素の基礎となるタイプをチェックしません。また、このため、EnumをINTに変換するために明示的なキャストを行う必要があります。
列挙タイプは継承されています System.Enum
, 、それらの間の変換は直接ではなく、ボクシング/ボクシングのものです。 C#3.0の仕様から:
列挙タイプは、名前のある定数を持つ個別のタイプです。すべての列挙タイプには、根本的なタイプがあり、バイト、sbyte、short、ushort、int、uint、long、またはlong、またはulngでなければなりません。列挙タイプの値のセットは、基礎となるタイプの値のセットと同じです。列挙タイプの値は、指定された定数の値に制限されません。列挙タイプは、列挙宣言によって定義されます
したがって、動物のクラスは派生しています System.Enum
, 、それは実際にです int
. 。ところで、もう一つの奇妙なことです System.Enum
から派生しています System.ValueType
, しかし、それはまだ参照タイプです。
a Enum
基礎となるタイプは、定数の値を保存するために使用されるタイプです。あなたの例では、あなたが値を明示的に定義していないにもかかわらず、C#はこれを行います。
enum Animal : int
{
Dog = 0,
Cat = 1
}
初めの、 Animal
整数値0と1を持つ2つの定数で構成されています。そのため、整数を明示的にキャストできます。 Animal
と Animal
整数に。合格した場合 Animal.Dog
Anを受け入れるパラメーターに Animal
, 、あなたが本当にしていることは、32ビットの整数値を渡すことです Animal.Dog
(この場合、0)。あなたが与えるなら Animal
新しい基礎となるタイプ、その後、値はそのタイプとして保存されます。
なぜ...たとえば、内部的にINTを保持し、明示的なキャストオペレーターとともにINTに変換できるようにすることは完全に有効です...列挙をシミュレートしましょう。
interface IEnum { }
struct MyEnumS : IEnum
{
private int inner;
public static explicit operator int(MyEnumS val)
{
return val.inner;
}
public static explicit operator MyEnumS(int val)
{
MyEnumS result;
result.inner = val;
return result;
}
public static readonly MyEnumS EnumItem1 = (MyEnumS)0;
public static readonly MyEnumS EnumItem2 = (MyEnumS)2;
public static readonly MyEnumS EnumItem3 = (MyEnumS)10;
public override string ToString()
{
return inner == 0 ? "EnumItem1" :
inner == 2 ? "EnumItem2" :
inner == 10 ? "EnumItem3" :
inner.ToString();
}
}
この構造体は、構造体とまったく同じ方法で使用できます...もちろん、タイプを反映しようとし、ISENUMプロパティを呼び出すと、FALSEを返します。
同等の列挙で、いくつかの使用法の比較を見てみましょう。
enum MyEnum
{
EnumItem1 = 0,
EnumItem2 = 2,
EnumItem3 = 10,
}
使用法の比較:
構造バージョン:
var val = MyEnum.EnumItem1;
val = (MyEnum)50;
val = 0;
object obj = val;
bool isE = obj is MyEnum;
Enum en = val;
enumバージョン:
var valS = MyEnumS.EnumItem1;
valS = (MyEnumS)50;
//valS = 0; // cannot simulate this
object objS = valS;
bool isS = objS is MyEnumS;
IEnum enS = valS;
一部の操作をシミュレートすることはできませんが、これはすべて私が言うつもりだったことを示しています...酵素は特別です、はい...どれくらい特別ですか?それほどではありません! =)