2009-08-12 11 views
9

nullにはタイプがありますか?内部的にどのようにnull値が表されますか?次のコードで何が起こっていますか?.Net/C#では、厳密に型指定されていますか?

void Foo(string bar) {...} 
void Foo(object bar) {...} 

Foo((string)null); 

編集:これまでの回答は非特定かつ高すぎました。私は、参照型オブジェクトは、ヒープ上のシンクブロックインデックス、型ハンドル、およびオブジェクトのフィールドを含む場所を指すスタック上のポインタで構成されていることを理解しています。オブジェクトのインスタンスをnullに設定すると、スタック上のポインタはどこに向いていますか?コードスニペットでは、C#コンパイラが呼び出すオーバーロードを決定するために単純に使用されるキャストはありますが、は実際にはのキャストが行われていませんか?

私はCLRの内部構造を理解している人が詳細な答えを探しています。

答えて

16

nullは型自体を持つことができないため、コードサンプルのstringへのキャストはnull型になりません。あなたはこの証明をしたい場合は、あなたがnullは、常に自分自身に等しいことを確認することができ、次のコードを実行し、関係なく、変数の型は、それが割り当てられていたものの次のとおりです。

string s = null; 
IPAddress i = null; 
Console.WriteLine(object.Equals(s, i)); // prints "True" 
Console.WriteLine(object.ReferenceEquals(s, i)); // prints "True" 

何キャストがないことは教えています選択するためにオーバーロードするコンパイラ。nullには型がないため、値がobjectまたはstringのオーバーロードを選択するかどうかはわかりません。だからあなたは「ここが文字列のように扱われるべきであるヌル値です」と言って助けているのです。


何が起こっているのかを見たい場合は、コードからILを見てください。だから、起こっていることすべてnullがスタック上にロードされているということである

ldnull 
call void ConsoleApplication1.Program::Foo(string) 

、および:メソッド呼び出しに関連するビットは(など、あなたの名前空間とクラス名に依存します)テキストのILに次のようなものですオーバーロード分解能がコンパイル時に実行されるので、コールするメソッドがILに焼き付けられるので、これは文字列を受け取るオーバーロードによって消費されます。

あなたがリンクをたどるしたくない場合は、その理由は、それがあるということです(ldnullが何をするか見てみたい、とそれだけでスタックにゼロをロードするためにldc.i4.0のようなものを使用した場合とは異なりますなぜ、その後this answerが表示された場合そうでなければCLRには存在しないサイズに依存しないゼロ)。

+0

それで、 'null'を参照しているとき、スタックには正確に何が入りますか? –

+0

@Matt - 低レベルの詳細を追加するように編集されました。これで十分ですか? –

+0

@Greg:ありがとう - 私は仕事で忙しかったので、ILとECMA仕様をチェックする時間がなかった。これは私の知的かゆみを傷つけました。 –

2

Foo(文字列バー)を呼び出します。

ヌルファインをキャストできます。

+0

明らかに - 私の編集をご覧ください。 –

1

nullキーワードは、オブジェクトを参照しないnull参照を表すリテラルです。 nullは参照型変数のデフォルト値です。 MSDNから。したがって、あなたの例ではStringのデフォルト値です。あなたがやっていることは前述のとおりにデフォルトを使用するのと同じです

// A null string is not the same as an empty string. 
string s = null; 
string t = String.Empty; // Logically the same as "" 

int equal = string.Compare((string)null, default(string)); 
2

あなたはnullに参照オブジェクトを設定し、特別な場所へのポインタ(後ろの)ポイントのメモリ内のそのnullと指定される。 nullにキャストすると、nullを指す特定のタイプのポインタが実際に作成されます。

(1)(2)によって実証されるように、これは、コンパイラが特定nullキャストに従ってIL発光オーバーロードを解決するため、ケースのようには思えません。

参照変数には型が関連付けられていることがわかります。私はこれがこのnullの後ろにあると思ったが、間違っていた。

opcode ldnull opcodeはヌル参照(タイプO)をスタックにプッシュします。また、このヌル参照の型が関連付けられている場合でも、callはこの参照を有効な引数のstringとして受け入れます。少なくとも、これは私の理解です。誰かがこれに訂正している場合は、私を修正してください。

1

NULLはマーカーです。データ型はありません。フィールドまたは変数に.NULLを割り当てると、値はNULLに変更されますが、フィールドまたは変数のデータ型は変更されません。

あなたの例では、魔法の方法が使用されると思われる理由(この場合、「void Foo(string bar){...}」は、文字列を引数として受け入れます)。

Foo((object)null)を呼び出すとしたら?

6

ヌルには型がありません。また、「コードスニペットでは、C#コンパイラで単純に使用されるキャストです」(「void Foo(オブジェクトバー){...}」)どちらの過負荷を呼び出すかを決めるために、実際には何も起こっていないのですか?まさに何が起こっているのですか?生成されたILを見てみましょう。

IL_0001: ldnull 
    IL_0002: call  void ConsoleApplication1.Program::Foo(string) 

ヌルがスタックにロードされていることに注目してください。これは単にヌルであり、文字列などのヌルではありません。重要なのは、ILが文字列を受け取るオーバーロードを明示的に呼び出す2行目です。ここでは、オブジェクトにキャスト、あなたが呼び出す場合、何が起こるかです:コンパイラが呼び出すために過負荷を知っているので、

IL_0001: ldnull 
    IL_0002: call  void ConsoleApplication1.Program::Foo(object) 

だから、はい、キャストはC#の成果物です。次のように

1

null値の種類をECMA-334によって定義される:

11.2.7ヌル型

ヌルリテラルは(§9.4.4.6)ヌル 値を評価し、その 参照がオブジェクト または配列を指していないか、または値が存在しないことを示すために使用されます。 null型には単一の値、ヌル値の があります。したがって、タイプがヌルタイプ の 式は、ヌル値だけを評価できます。 null型を明示的に記述する方法がないため、宣言型で を使用する方法はありません。

null型は、オブジェクトの反対側の型階層の下位型です。ヌル型は、ヌル可能なすべての型のサブタイプであると考えることができます。ヌル値は、ヌル可能な式が発生するたびに使用できます。

これは、受信者がnull可能な型の操作がnullの可能性があり、実行時に例外が発生して操作が失敗する可能性があるため、型システムに弱点をもたらします。強力な型システムでは、型が提供する操作は、受信者がその型のものである場合に保証されます(つまり、nullポインタ例外を受け取らない、実際にはnull型は、ヌル値は、あなたが使用していると思ったどのような型の式からも得られますが)。

コードでは、メソッドがオーバーロードされ、引数を生成する式の静的型がオーバーロードの解決に使用されます。文字列へのキャストのため、式には型文字列があります(null型は文字列のサブタイプですが、これはC#型のシステムでは安全です)。最初の近似として、コンパイラは可視の最も特定のオーバーロードを選択するので、オブジェクトバージョンではなくFooの文字列バージョンを選択します。

関連する問題