2

重複の可能性:
C# コンパイラが GetType() メソッド呼び出しに対して callvirt 命令を発行するのはなぜですか?

クラスのインスタンス メソッドを呼び出すと、C# コンパイラが callvirtそのメソッドを呼び出す命令を発行するのを見ましたが、これはなぜですか?

すべてのインスタンス メソッドがコンパイラによって扱われるというvirtual methodsことですか、この謎は何ですか?

4

3 に答える 3

14

C# 言語仕様で行われた約束を実装するためにあります。つまり、null 参照を介してクラスのインスタンス メソッドを呼び出すことは合法ではありません。これは当たり前の機能のように聞こえるかもしれませんが、実際には OOP 言語ではそれほど一般的ではありません。特に、C++/CLI コンパイラにはそれがありません。そして、CLI 仕様にはそれがありません。C++ のようなアンマネージ言語にはそれがありません。

インスタンス メソッドが非静的クラス メンバーを使用しない場合は、良い結果が得られることもあります。もちろん、そのようなメソッドは静的であるべきですが、それは必須でも強制でもありません。

C# の要件は非常に優れており、NullReferenceException診断がはるかに簡単になります。それらはインスタンス メソッド内ではなく呼び出しサイトで生成されるため、オブジェクト参照が null であることを明確にします。this参照がメソッド内で null であることを理解することは、特に見えないため、ちょっと難しいです。アドレスが実際には null ではないため、さらに複雑になります。クラスのフィールドにアクセスすると、0 からオフセットされたアドレスが生成されます。オブジェクトが 64 キロバイトを超える巨大な場合、これは安全ではありません。このような大きなオブジェクトの最後にあるフィールドにアクセスしても、必ずしもプロセッサ例外が生成されるとは限りません。ランダムなジャンクを読み取るだけです。または、書き込むとメモリが破損します。

そこで C# チームは、null テストを安価に実装する方法を探しました。そして、callvirtIL命令で1つ見つかりました。とは異なりcall、これCLI 仕様の例外を約束します。非常に安価なテストで、1 つのマシン コード命令のみが必要です。また、プロセッサの分岐予測ロジックが間違って推測した場合、非常にコストがかかる分岐を必要としません。

また、String.Equals() にこの謎のコードが含まれている理由もわかりました。

public override bool Equals(Object obj) {
    if (this == null)
        throw new NullReferenceException();
    // etc...
}
于 2013-01-11T18:35:14.953 に答える
12

ハンスとマイクの答えは正しいです。ちょっとした追加情報を追加するだけです:

  • 命令は、callvirt非仮想メソッドで正確に正当であると明示的に文書化されているため、null チェック動作が得られます。

  • C# コンパイラが、非仮想呼び出しが null レシーバーを持つ可能性がないことを証明するまれなケースでは、フォールバックしcallて、null チェックの実行にかかるナノ秒を節約します。たとえば、受け取った式がnullにならないことをコンパイラが認識しているからではなく、(new C()).InstanceMethod()として生成する必要があります。(割り当てが失敗した場合、例外がスローされるため、呼び出しは実行されません。)callcallvirt

于 2013-01-11T19:47:11.937 に答える
2

簡単な答え:その方が安全です。

callvirt最初にオブジェクトが null かどうかをチェックし、そうであれば例外をスローします。

callオブジェクトを null にすることはできないため、静的メソッドを呼び出すと引き続き が使用されることに気付くでしょう。

ここに少し歴史があります。

于 2013-01-11T17:23:55.713 に答える