重複の可能性:
C# コンパイラが GetType() メソッド呼び出しに対して callvirt 命令を発行するのはなぜですか?
クラスのインスタンス メソッドを呼び出すと、C# コンパイラが
callvirt
そのメソッドを呼び出す命令を発行するのを見ましたが、これはなぜですか?
すべてのインスタンス メソッドがコンパイラによって扱われるというvirtual methods
ことですか、この謎は何ですか?
重複の可能性:
C# コンパイラが GetType() メソッド呼び出しに対して callvirt 命令を発行するのはなぜですか?
クラスのインスタンス メソッドを呼び出すと、C# コンパイラが
callvirt
そのメソッドを呼び出す命令を発行するのを見ましたが、これはなぜですか?
すべてのインスタンス メソッドがコンパイラによって扱われるというvirtual methods
ことですか、この謎は何ですか?
C# 言語仕様で行われた約束を実装するためにあります。つまり、null 参照を介してクラスのインスタンス メソッドを呼び出すことは合法ではありません。これは当たり前の機能のように聞こえるかもしれませんが、実際には OOP 言語ではそれほど一般的ではありません。特に、C++/CLI コンパイラにはそれがありません。そして、CLI 仕様にはそれがありません。C++ のようなアンマネージ言語にはそれがありません。
インスタンス メソッドが非静的クラス メンバーを使用しない場合は、良い結果が得られることもあります。もちろん、そのようなメソッドは静的であるべきですが、それは必須でも強制でもありません。
C# の要件は非常に優れており、NullReferenceException
診断がはるかに簡単になります。それらはインスタンス メソッド内ではなく呼び出しサイトで生成されるため、オブジェクト参照が null であることを明確にします。this参照がメソッド内で null であることを理解することは、特に見えないため、ちょっと難しいです。アドレスが実際には null ではないため、さらに複雑になります。クラスのフィールドにアクセスすると、0 からオフセットされたアドレスが生成されます。オブジェクトが 64 キロバイトを超える巨大な場合、これは安全ではありません。このような大きなオブジェクトの最後にあるフィールドにアクセスしても、必ずしもプロセッサ例外が生成されるとは限りません。ランダムなジャンクを読み取るだけです。または、書き込むとメモリが破損します。
そこで C# チームは、null テストを安価に実装する方法を探しました。そして、callvirt
IL命令で1つ見つかりました。とは異なりcall
、これはCLI 仕様の例外を約束します。非常に安価なテストで、1 つのマシン コード命令のみが必要です。また、プロセッサの分岐予測ロジックが間違って推測した場合、非常にコストがかかる分岐を必要としません。
また、String.Equals() にこの謎のコードが含まれている理由もわかりました。
public override bool Equals(Object obj) {
if (this == null)
throw new NullReferenceException();
// etc...
}
ハンスとマイクの答えは正しいです。ちょっとした追加情報を追加するだけです:
命令は、callvirt
非仮想メソッドで正確に正当であると明示的に文書化されているため、null チェック動作が得られます。
C# コンパイラが、非仮想呼び出しが null レシーバーを持つ可能性がないことを証明するまれなケースでは、フォールバックしcall
て、null チェックの実行にかかるナノ秒を節約します。たとえば、受け取った式がnullにならないことをコンパイラが認識しているからではなく、(new C()).InstanceMethod()
として生成する必要があります。(割り当てが失敗した場合、例外がスローされるため、呼び出しは実行されません。)call
callvirt
簡単な答え:その方が安全です。
callvirt
最初にオブジェクトが null かどうかをチェックし、そうであれば例外をスローします。
call
オブジェクトを null にすることはできないため、静的メソッドを呼び出すと引き続き が使用されることに気付くでしょう。
ここに少し歴史があります。