C# dynamicの使いどころ

Visual Studio 2010ではC#にdynamicが追加されました。

JavaScript, php, rubyの様に実行時に型を解決しメソッドをバインド(レイトバインディング)するものです。Visual Basicでは以前からソース毎にレイトバインディングすることを指定できましたが、c#のdynamicでは変数毎に指定できるのです。

現状のC#の言語仕様では残念ながら型のつじつま合わせのためのコードを書かなければなりません。これを部分的に軽減するためにdynamicが使えます。

オープンジェネリックメソッド呼び出し

例えば、型引数を持ったクラスBase<T>のメソッドMethod1を他のクラスから呼び出そうとしても、型引数が決まらない(オープンジェネリック型)ためにキャストできないので呼び出せないのです。オープンジェネリック型のメソッドを呼び出そうとする場合には、リフレクションを使用する方法の他に、型引数を持たないBase<T>の基底クラスBaseを作成してMethod1を仮想メソッドとして持たせ間接的にBase<T>Method1を呼び出す方法があります。Netframeworkにもこのパターンはよく現れます。恐らくVisual Studio 2008までの場合は、この方法がベストプラクティスと思われます。この方法の欠点は型のつじつま合わせのためだけに2つのクラスを作らなければならないことです。そしてコーディング量が大変増えてしまいます。

オープンジェネリック型変数の宣言にdynamicを使えば基底クラスを作らなくてもメソッド呼び出しができるようになります。欠点としてはコンパイル時型チェックが効かなくなることです。

今後C#の言語仕様が改善されて共変・反変の機能が強力にサポートされ、dynamicを使わなくてもオープンジェネリック型メソッドを呼ぶことができるようになることを期待します。

Visitorパターン代替

Visitorパターンでは、Visitorが、継承されたそれぞれのAcceptorのAcceptメソッドを呼ぶと、Acceptorの型に応じたVisitor.Visitメソッドを呼び返します。この仕組みをdynamicで実現すれば、AcceptorのAcceptを経由しないでディスパッチできます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class BaseItem
{
}

class Leaf : BaseItem
{
}

class Node : BaseItem
{
public List<BaseItem> Branch { get; private set; }

public Node()
{
Branch = new List<BaseItem>();
}
}

class Visitor
{
public void Visit(Leaf item)
{
//何らかの処理
}

public void Visit(Node item)
{
//何らかの処理
foreach (var child in item.Branch) {
CallVisit(child);
}
}

public void CallVisit(dynamic item)
{
Visit(item);
}
}

注意点

dynamicで宣言された変数を通してメソッドを呼ぶときに引数に変数を指定した場合、変数宣言の型でメソッドがディスパッチされるか、変数に格納された値の型でディスパッチされる値の型でディスパッチされるか配慮しなければならないことがあります。

1
2
3
dynamic callee = new Callee();
object arg = "TestString";
callee.Method(arg);

例えば上の例ではメソッドに渡される引数はobjectで宣言されていますが、変数に代入されている値はstringです。この場合、void Method(string param)void Method(object param)のどちらが呼び出されるかと言うことです。

答えは変数宣言の型でメソッドがディスパッチされるです。この場合はvoid Method(object param)が呼び出されます。これを間違いvoid Method(object param)を用意しなければ最も適しているオーバーロード メソッドには無効な引数がいくつか含まれていますとのエラーが発生します。

変数に格納された値の型でディスパッチして欲しい場合は変数宣言もdynamicにします。

1
2
3
dynamic callee = new Callee();
dynamic arg = "TestString";
callee.Method(arg);