C#のディスパッチ手法まとめ

型がわからない処理から特定の型に対する処理を呼び出すためには、ディスパッチが不可欠です。

この記事では「ディスパッチ」を「型が一つに定まらない処理(A)から特定の型に対する処理(B)を呼び出す操作」として用います。

# 静的ディスパッチ
## 1. ポリシーパターン
```CSharp
void A<T>(T t) where T : I
{
    t.B();
}

interface I
{
    void B();
}

struct S : I
{
    public void B() { ... };
}
```

インライン展開が強制的にされるので最速です。

## 2. STD (Static Type Dictionary)
```CSharp
void A<T>(T t)
{
    STD<T>.b(t);
}

class C
{
    public void B() { ... }
}

static class STD<T>
{
    public Action<T> b;

    static STD()
    {
        b = x => x.B();
    }
}
```

デリゲートを挟みますが十分早いです。

# 動的ディスパッチ
## インターフェース
```CSharp
void A(I i)
{
    i.B();
}

class C : I
{
    public void B();
}

interface I
{
    void B();
}
```

シンプル。

インライン化が効くと静的ディスパッチになるので最速になりもします。

## ポリシーパターン風(無意味)
```CSharp
virtual void A<T>(T t) where T : I
{
    t.B();
}

interface I
{
    void B();
}

struct S : I
{
    public void B() { ... };
}
```

一見正しいポリシーパターンに見えますが、仮想メソッド(`virtual`)には通用しません。ただのインターフェース経由の呼び出しになります。

## 自作vtable
```CSharp
// 呼び出すときは、`C c = ...; A(c.ToObjectWithTable());`
void A(ObjectWithTable objectWithTable)
{
    objectWithTable.B();
}

class C
{
    public void B() { ... };

    public ToObjectWithTable() => new(this, x => Unsafe.As<C>(x).B());
}

struct ObjectWithTable
{
    object o;
    Action<object> b;
    public void B() => b(o);
    public ObjectWithTable(object o, Action<object> b) { ... };
}
```


## 自作vtable(unsafe)
```CSharp
// 呼び出すときは、`C c = ...; A(c.ToObjectWithTable());`
void A(ObjectWithTable objectWithTable)
{
    objectWithTable.B();
}

class C
{
    public void B() { ... };

    unsafe ObjectWithTable ToObjectWithTable() => new(this, (delegate managed<object, void>*)&Extensions.B);
}

static Extentions
{
    public static void B(this C @this) => @this.B();
}

unsafe struct ObjectWithTable
{
    object o;
    delegate managed<object, void>* b;
    public void B() => b(o);
    public ObjectWithTable(object o, delegate managed<object, void>* b) { ... };
}
```

インターフェースでやってることとあまり変わらない。

## ハッシュテーブル

```CSharp
void A(object o)
{
    table[o.GetType()](o);
}

class C
{
    public void B() { ... }
}

static Dictionary<Type, Action<object>> table = new() 
{ 
    { typeof(C), x => Unsafe.As<C>(x).B(); }
};
```

遅い。

## STD
```CSharp
void A<T>(T t)
{
    STD<T>.b();
}

class C
{
    public void B() { ... }

    static C()
    {
        STD<C>.b = x => x.B();
    }
}

static class STD<T>
{
    public Action<T> b;
}
```

既存の型(=使用者が新たにメソッドを追加できない型)の場合にも使えて、そこそこ高速(静的の4倍ぐらい)。

## 配列テーブル
```CSharp
void A<T>(T t)
{
    Unsafe.As<Action<C>>(table[STD<C>.typeId])(t);
}

class C
{
    public void B() { ... }

    static C()
    {
        table[STD<C>.typeId] = (Action<C>)(x => x.B());
    }
}

static object[] table = new object[];

static int typeId_next;
static class STD<T>
{
    public static int typeId;

    static STD()
    {
        typeId = Interlocked.Increment(ref typeId_next);
    }
}
```

STD(動的)の仕組みは恐らくこんなものだろうと推測する。

## is
```CSharp
void A(object o)
{
    if (o is C c) c.B();
    else if (o is D d) d.B();
    else if ...
}

class C
{
    public void B() { ... }
}
```

かなり遅い

## dynamic
```CSharp
void A(dynamic d)
{
    d.B();
}

class C
{
    public void B() { ... }
}
```

[これ](https://qiita.com/drafts/9aa4ebbcb504edbeae22/edit#is)と等価。

## リフレクション
```CSharp
void A(object o)
{
    o.GetType().GetMethod("B").CreateDelegate<Action>(o).Invoke();
}

class C
{
    public void B();
}
```

最強に遅い。