2016-07-30 2 views
4

Int32.ToString()がcallvirtではなくcall命令を発行するのはなぜですか?次のコードについて

struct Test 
{ 
    public override string ToString() 
    { 
     return ""; 
    } 
} 

public class Program 
{ 
    public static void Main() 
    { 
     Test a = new Test(); 
     a.ToString(); 
     Int32 b = 5; 
     b.ToString(); 
    } 
} 

コンパイラは、次のIL発する:値型Test両方ので

.locals init ([0] valuetype ConsoleApplication2.Test a, 
      [1] int32 b) 
    IL_0000: nop 
    IL_0001: ldloca.s a 
    IL_0003: initobj ConsoleApplication2.Test 
    IL_0009: ldloca.s a 
    IL_000b: constrained. ConsoleApplication2.Test 
    IL_0011: callvirt instance string [mscorlib]System.Object::ToString() 
    IL_0016: pop 
    IL_0017: ldc.i4.5 
    IL_0018: stloc.1 
    IL_0019: ldloca.s b 
    IL_001b: call  instance string [mscorlib]System.Int32::ToString() 
    IL_0020: pop 
    IL_0021: ret 

およびInt32ToString()メソッドをオーバーライドし、私はボクシングの両方a.ToString()に生じないと思いますb.ToString()。だから、私はなぜコンパイラが + callvirtTestに、callInt32のために出すのだろうか?

答えて

6

これは、プリミティブ型のため、コンパイラによって行われる最適化です。

カスタム構造体でも、callvirtは、constrained.オペコードのために、実行時に実際にcallとして実行されます(メソッドがオーバーライドされた場合)。これにより、どちらの場合でもコンパイラは同じ命令を発行し、実行時にそれを処理させることができます。 MSDNから

thisTypeが値型であり、thisTypeptr次いでmethodを実装する場合

thisTypeによる方法を実施するため、call方法指示thisポインタとして未修飾渡されます。

そして:

constrainedオペコードはILコンパイラはptrは値型または参照型であるかどうかとは無関係に均一な方法仮想関数の呼び出しを行うことができます。 thisTypeがジェネリック型変数である場合を想定していますが、制約付きプレフィックスは非永続型に対しても機能し、値型と参照型の区別を隠す言語で仮想呼び出しを生成する複雑さを軽減できます。

私はこの最適化の公式文書を知らないが、MayUseCallForStructMethod methodのRoslynレポの注釈を見ることができます。

なぜこの最適化が非プリミティブ型のランタイムに延期されるのかについては、実装が変更される可能性があるためです。最初にToStringのオーバーライドがあったライブラリを参照し、DLLを(再コンパイルせずに)オーバーライドが削除されたものに変更するとします。これは、実行時例外を引き起こしています。プリミティブの場合、それは起こらないと確信することができます。

+0

ありがとう、私はそれがコンパイラの最適化作業であると推測します。しかし私は私の推測を支える資料を見つけることはできません。ですから、プリミティブ型のこの特別な最適化に関するドキュメントを提供できれば幸いです。再度、感謝します。 –

+0

@LifuHuang私の答えを更新しました。 –

+0

@Eli Arbelありがとう、あなたは本当に私の質問を解決しました:) –

0

Intは、フレームワークは密閉型を提供であり、他のいくつかのタイプのオーバーライドがToString方法をint型ということが起こることは決してありませんので、コンパイラは、それは常にintタイプで提供ToString()メソッドの実装を呼び出す必要があることを知っているので、それがないので、それはあります呼び出す実装を理解するためにcallvirtを使用する必要はありません。

プリミティブ型の場合、コンパイラはToStringのどの実装が呼び出されるかを知っていますが、カスタム値型を作成するときには以前に存在しなかった新しいものなので、コンパイラはそれを知らず、それはデフォルトでObjectから継承するので、コンパイラはcallvirtを実行して、オーバーライドされていない場合はオーバーライドされていない場合はカスタムタイプの実装を提供する必要があります。

次の既存のSOポストはこれを理解する上であなたを助けることができる:

Call and Callvirt

+1

しかし、すべての値の型は暗黙的に封印されているので、この点から、 'Int32'と' Test'は同じように動作するはずです。私が間違っていれば私を修正してください。 –

+0

はいフレームワークはシールタイプで、特別なケースです –

+0

申し訳ありませんが、あなたの "特殊なケース"がここで何を意味するのか分かりません。詳細を教えてください。 –

関連する問題