call と callvirt その3 : struct と callvirt

MSDNライブラリの Constrained の説明によると、「通常、callvirt 命令は値型では有効ではありません。」だそうだけど、普通に動いた。ILの世界はおおらかだ。

using System;
using System.Reflection.Emit;

struct Baz
{
  private int n;
  public int Value { get { return n; } }
  public Baz( int n ) { this.n = n; }
  public override string ToString() { return "Baz: " + n; }
}

class Class5
{
  static void Main()
  {
    Baz baz = new Baz( 2 );

    Console.WriteLine( " baz.Value: {0}", baz.Value );

    // Valueプロパティを読む
    DynamicMethod dm = new DynamicMethod( "", typeof( int ),
      new Type[] { typeof( Baz ) }, typeof( Class5 ) );
    ILGenerator ilgen = dm.GetILGenerator();

    ilgen.Emit( OpCodes.Ldarga_S, ( byte ) 0 );
    //ilgen.Emit( OpCodes.Call, typeof( Baz ).GetProperty( "Value" ).GetGetMethod() );
    ilgen.Emit( OpCodes.Callvirt, typeof( Baz ).GetProperty( "Value" ).GetGetMethod() );
    ilgen.Emit( OpCodes.Ret );
    Console.WriteLine( dm.Invoke( null, new object[] { baz } ) );

    // ToString() を呼ぶ
    DynamicMethod dm2 = new DynamicMethod( "", typeof( string ),
      new Type[] { typeof( Baz ) }, typeof( Class5 ) );
    ILGenerator ilgen2 = dm2.GetILGenerator();

    ilgen2.Emit( OpCodes.Ldarga_S, ( byte ) 0 );
    //ilgen2.Emit( OpCodes.Call, typeof( Baz ).GetMethod( "ToString" ) );
    ilgen2.Emit( OpCodes.Callvirt, typeof( Baz ).GetMethod( "ToString" ) );
    ilgen2.Emit( OpCodes.Ret );
    Console.WriteLine( dm2.Invoke( null, new object[] { baz } ) );
    
    Console.ReadKey();
  }
}

この例では、call/callvirt共に正常に動く。
ただし、BazがToStringをオーバーライドしていないとヌルポになる。で、それを解決してくれるのが constrained。でも、残念ながら値型のインスタンスメソッド(非仮想メソッド)には対応してないみたい。値型の仮想メソッドには対応している。