call の代わりに callvirt を使用すると、スタック オーバーフローまで再帰呼び出しが発生するのはなぜですか?
そのため、コードは次とまったく同じです。
override string ToString()
{
return this.ToString();
}
指定されたメソッドが ToString の最もオーバーライドされたバージョンである場合、これは明らかに無限再帰です。
call が使用されている場合、メソッドを呼び出すために使用するインスタンス変数が null かどうかを確認するのはなぜですか?
質問は虚偽を前提としているため、回答できません。call 命令は、レシーバーへの参照が null かどうかをチェックしないため、call 命令が null をチェックする理由を尋ねても意味がありません。
それをいくつかのより良い質問に言い換えてみましょう:
C# コンパイラはどのような状況で call と callvirt を生成しますか?
C# コードが仮想メソッドで非仮想呼び出しを実行している場合、コンパイラは callvirt ではなく呼び出しを生成する必要があります。これが実際に発生するのは、使用して仮想メソッドを呼び出すときだけです。base
C# コードが仮想呼び出しを実行している場合、コンパイラはcallvirt を生成する必要があります。
C# コードが非仮想メソッドで非仮想呼び出しを実行している場合、コンパイラは call または callvirt のいずれかを生成することを選択できます。どちらでも動作します。C# コンパイラは通常、callvirt の生成を選択します。
call 命令は自動的に null チェックを行いませんが、callvirt は行います。C# コンパイラが callvirt の代わりに呼び出しを生成することを選択した場合、null チェックも生成する義務がありますか?
いいえ。レシーバーが null ではないことが既にわかっている場合、C# コンパイラは null チェックをスキップできます。たとえば(new C()).M()
、非仮想メソッド M について述べた場合、コンパイラがcall
null チェックなしで命令を生成することは合法です。(1) メソッドは仮想ではないため、callvirt
;である必要はありません。使用するかどうかを選択できますcallvirt
。また、(2)new C()
は決して null ではないことがわかっているため、null チェックを生成する必要はありません。
C# コンパイラは、レシーバーが null でないことを認識していない場合、callvirt を生成するか、null チェックとそれに続く呼び出しを生成します。