2009-05-10 7 views
27

なぜこれが起きているのか不思議です。次のコード例を読んでください、各セクションの下のコメントに放出された対応するIL:コンパイラは、第二のセクションの最初のセクションが、callためcallvirtを発するなぜC#コンパイラがGetType()メソッド呼び出しのcallvirt命令を発行するのはなぜですか?

using System; 

class Program 
{ 
    static void Main() 
    { 
     Object o = new Object(); 
     o.GetType(); 

     // L_0001: newobj instance void [mscorlib]System.Object::.ctor() 
     // L_0006: stloc.0 
     // L_0007: ldloc.0 
     // L_0008: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType() 

     new Object().GetType(); 

     // L_000e: newobj instance void [mscorlib]System.Object::.ctor() 
     // L_0013: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType() 
    } 
} 

?コンパイラが非仮想メソッドのcallvirt命令を発行する理由はありますか?コンパイラが非仮想メソッドのためにcallvirtを発行するケースがあると、型安全性に問題が生じますか?

+1

非常に良い質問...私は自分の書籍に手を差し伸べました。 – Gishu

+15

集計:ケース(1)仮想メソッドを呼び出す:callvirtを生成します。ケース(2)null可能な受信側でインスタンスメソッドを呼び出す:安価なヌルチェックを得るためにcallvirtを生成する - はい、これは型保証されています。ケース(3)既知のヌル可能でない受信側でインスタンスメソッドを呼び出す:_avoid_nullチェックの呼び出しを生成する。最初の例はカテゴリ(2)に分類され、2番目の例はカテゴリ(3)に分類されます。コンパイラは、newが決してnullを返さないことを知っているので、再度チェックする必要はありません。 –

+0

Ericのおかげで、ヌルチェックは意味があります。あなたのコメントと紀州の答えは物事をもっとはっきりさせます! :) –

答えて

20

安全に遊ぶだけです。

技術的にはC#コンパイラは、静的メソッド値型で定義されている&方法については常に使用callvirt

、それはcallを使用しません。大半はcallvirt IL命令で提供されています。

callが「呼び出しに使用されているオブジェクト」がヌルではないと仮定している点が、2つの投票の間の違いです。一方、callvirtはnullでないかどうかをチェックし、必要に応じてNullReferenceExceptionをスローします。

  • 静的メソッドの場合、オブジェクトは型オブジェクトであり、nullにはできません。値型の場合は同値。したがって、callがそれらのために使用されます - より良いパフォーマンス。
  • 他の人にとって、言語設計者はcallvirtに行くことに決めました。そのため、JITコンパイラは、呼び出しに使用されているオブジェクトがnullでないことを確認します。非仮想インスタンス・メソッドであっても、パフォーマンスに対する安全性を重視していました。

関連項目:ジェフリヒターはこの時、より良い仕事をしていません - CLRでの彼の「デザインの種類」の章ではC#第2版を経て

+0

私は両方ともデバッガをオフにしてコールを使うと思います(最初のコールバックはデバッグで2番目の行にブレークポイントを置き、oの値をnullに変更できるためです)](http://stackoverflow.com/a/193955/2850543)。 –

1

コンパイラは、最初の式でoという実数型を認識しませんが、2番目の式では実数型を認識します。一度に1つのステートメントだけを見ているようです。

C#は最適化のためにJITに大きく依存するため、これは問題ありません。このような単純なケースでは、両方の呼び出しが実行時にインスタンス呼び出しになる可能性が非常に高いです。

私はcallvirtが非仮想メソッドのために放出されたとは考えていませんが、たとえそれがあったとしても、(明らかな理由により)メソッドがオーバーライドされることはないため、問題ありません。

+0

仮想メソッドのcallvirtについて説明しています。しかし、GetTypeは仮想ではありません。これは、CLRの腸の深いどこかで実装されたextern関数です(おそらく、オブジェクトのvtableなどに格納されているフィールドを返します)。すべてのオブジェクトに対して同じ方法です。 – Niki

+0

十分に公正 - 私はGetTypeが仮想だと仮定しました。私の悪い。私はダスティンの答えが好きです。 – zildjohn01

0

GetTypeをオーバーライドした可能性のある別のタイプのダウンキャストされたインスタンスが最初に変数に割り当てられている可能性があります。 2番目の数字はObject以外のものになることはありません。

27

thisを参照してください。古いブログ記事は、Eric Gunnersonです。ここで

はポストのテキストです:

なぜC#が常にcallvirtを使用していますか?

この質問は内部のC#エイリアスにありました。私はその答えが一般的な関心事であると考えました。それは答えが正しいことを前提としています - それはずっとずっと続いています。

.NET IL言語は、callvirtが仮想関数の呼び出しに使用されているcallおよびcallvirt命令の両方を提供します。しかし、C#が生成するコードを調べると、仮想関数が含まれていない場合でも "callvirt"が生成されることがわかります。それはなぜそれをするのですか?

私は、私が持っている言語設計ノートを取りに行きました。そして、彼らはかなり明確に、私たちがcallvirtを12/13/1999に使用することを決めたと述べています。残念ながら、彼らはそれをするための私たちの論理的根拠を捉えていないので、私は私の記憶から離れなければならないでしょう。

誰か(おそらくその時点でC#という名前ではないと思っていたC#を使用している.NETグループ)からレポートを受け取りましたが、nullポインタにメソッドを呼び出したコードを書いていましたが、メソッドがどのフィールドにもアクセスしなかったため(つまり、「this」はnullでしたが、メソッド内には何もありませんでした)、例外は発生しませんでした。その方法は、この点を使用して例外を投げた別の方法を呼び出し、少し頭を引っ張ってしまいました。彼らはそれを理解した後、私たちにそれについてのメモを送った。

nullインスタンスでメソッドを呼び出すことができるのはちょっと変わったと思いました。 Peter Goldeは、perfvの影響が常にcallvirtを使用していたことを確認するためにいくつかのテストを行いました。

+3

'string.IsNullOrEmpty'のようなメソッドをヌル文字列で使用できるようにするのに役立つので、非仮想メソッドが' callvirt'なしで呼び出さなければならないことを指定する方法はありません。 – supercat

+0

@supercatそれは、オブジェクトがヌルであっても、拡張メソッドを使用することです。それは、拡張子の最初のパラメータで引き続き渡されます。 – vexe

+0

残念ながら、ジェネリック関数でジェネリック型の拡張メソッドを使用して、静的メソッドの(実効的な)実行時の決定を作成することはできません。 – NetMage

3

(perhaps-)はさておき面白いと... GetType()がありますそれはvirtualでないという点で珍しいです - これはvery, very odd thingsにつながります。

(実際の質問とは多少話題が違うため、wikiとしてマークされています)

関連する問題