ILの call と callvirt の違い その2

nullもガッと鳴いたよ。

using System;
using System.Reflection.Emit;

class Duck
{
  public void Say() { Console.WriteLine( "ガッ" ); }
}

class Class3
{
  static void Main()
  {
    // void Do() メソッドを作る
    DynamicMethod dm = new DynamicMethod( "Do", null, null, typeof( Class3 ) );
    ILGenerator ilgen = dm.GetILGenerator();

    // null をスタックに push
    ilgen.Emit( OpCodes.Ldnull );
    // Duck.Say() を呼ぶ
    ilgen.Emit( OpCodes.Call, typeof( Duck ).GetMethod( "Say" ) );
    //ilgen.Emit( OpCodes.Callvirt, typeof( Duck ).GetMethod( "Say" ) );

    ilgen.Emit( OpCodes.Ret );

    // Do() を実行
    dm.Invoke( null, null );
    Console.ReadKey();
  }
}

ducktypingどころの話じゃなかった。Duckは一度も生成していない。
正確にはcallそのものはオブジェクト参照がスタックになくてもよい。(thisを必要としない)static callができるのでこれはまぁ当然。ただし、Duck.Say()は暗黙にthisを取るのでthisは必要。このコードではthisがnull。
対して、callvirtはオブジェクト参照が必要。必要といってもこっちもチェックしてるんだかどうなんだか。
↑のコードのcallをEmitしている行をコメントアウトし、callvirtをEmitしている行を活かすと内部例外がNullReferenceExceptionのTargetInvocationExceptionが出る。
パラメータチェックが甘いと解釈すべきなのかなぁ?