読者です 読者をやめる 読者になる 読者になる

インスタンスメソッド,this, デリゲートへの束縛可能性

.NET

インスタンスメソッドの型は何?

public class Foo
{
    public string Name { get; set; }
    public string GetStr(int i)
    {
        if( this != null )
            return string.Format("My name is {0}", this.Name);
        else
            return string.Format("this is null!");
    }
}

こういうクラスの Foo.GetStr の型を考えてみる.
ソースコード上で引数はひとつでも,呼び出し時には型 Foo の暗黙の引数 this が最初の引数として存在する.
実際,.NET の Common Type System (CTS) はこいつに対応する MethodInfo から,Func<Foo, int, string> へのバインドを許している.

using System;

static class Program
{
    static void Main(string[] args)
    {
        var foo = new Foo { Name = "tekitou" };

        Func<Foo, int, string> f1 = (_this, i) => _this.GetStr(i);
        // no exception
        Console.WriteLine(f1(foo, 1));

        var mi = typeof(Foo).GetMethod("GetStr");
        var f2 = Delegate.CreateDelegate(typeof(Func<Foo, int, string>), mi)
                     as Func<Foo, int, string>;

        // no exception
        Console.WriteLine(f2(foo, 1));
    }
}
My name is tekitou
My name is tekitou

this が null という可能性

this が null という状況を作り出したいとき,実は上記の方法が使える.

using System;

static class Program
{
    static void Main(string[] args)
    {
        Func<Foo, int, string> f1 = (_this, i) => _this.GetStr(i);
        // NullReferenceException
        // Console.WriteLine(f1(null, 1));

        var mi = typeof(Foo).GetMethod("GetStr");
        var f2 = Delegate.CreateDelegate(typeof(Func<Foo, int, string>), mi)
                     as Func<Foo, int, string>;

        // no exception
        Console.WriteLine(f2(null, 1));
    }
}
this is null!

仮想メソッドではどうか?

CTS の面白いところは,基底クラスの仮想メソッドから,第 0 引数を「ほどいた」デリゲートを作成したときに,仮想メソッドのセマンティクスがちゃんと維持されるところ.もちろんこのとき this は null にできない.

using System;

public class Foo
{
    public override string ToString()
    {
        return "I'm Foo";
    }
}

static class Program
{
    static void Main(string[] args)
    {
        Func<object, string> toString1 = o => o.ToString();
        Console.WriteLine(toString1(new object()));
        Console.WriteLine(toString1(new Foo()));

        var mi = typeof(object).GetMethod("ToString");
        var toString2 = Delegate.CreateDelegate(typeof(Func<object, string>), mi)
                           as Func<object, string>;

        Console.WriteLine(toString2(new object()));
        Console.WriteLine(toString2(new Foo()));

        // NullReferenceException
        // Console.WriteLine(toString2(null));
    }
}
System.Object
I'm Foo
System.Object
I'm Foo