dynamic
リフレクションAPIの代わりに型を使用することで、実行時にのみ既知の型パラメーターを使用してジェネリックメソッドを呼び出すことが大幅に簡略化されます。
この手法を使用するには、Type
クラスのインスタンスだけでなく、実際のオブジェクトから型を知る必要があります。それ以外の場合は、そのタイプのオブジェクトを作成するか、標準のリフレクションAPI ソリューションを使用する必要があります。Activator.CreateInstanceメソッドを使用してオブジェクトを作成できます。
ジェネリックメソッドを呼び出したい場合、「通常の」使用では、その型が推論されていたはずですが、不明な型のオブジェクトをにキャストするだけdynamic
です。次に例を示します。
class Alpha { }
class Beta { }
class Service
{
public void Process<T>(T item)
{
Console.WriteLine("item.GetType(): " + item.GetType()
+ "\ttypeof(T): " + typeof(T));
}
}
class Program
{
static void Main(string[] args)
{
var a = new Alpha();
var b = new Beta();
var service = new Service();
service.Process(a); // Same as "service.Process<Alpha>(a)"
service.Process(b); // Same as "service.Process<Beta>(b)"
var objects = new object[] { a, b };
foreach (var o in objects)
{
service.Process(o); // Same as "service.Process<object>(o)"
}
foreach (var o in objects)
{
dynamic dynObj = o;
service.Process(dynObj); // Or write "service.Process((dynamic)o)"
}
}
}
そして、これがこのプログラムの出力です:
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
item.GetType(): Alpha typeof(T): System.Object
item.GetType(): Beta typeof(T): System.Object
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
Process
(GetType()
メソッドを使用して)渡された引数の実数型と(typeof
演算子を使用して)汎用パラメーターの型を書き込む汎用インスタンスメソッドです。
オブジェクト引数をdynamic
型にキャストすることにより、実行時まで型パラメーターの提供を延期しました。引数を指定Process
してメソッドが呼び出されると、dynamic
コンパイラーはこの引数のタイプを気にしません。コンパイラーは、実行時に渡された引数の実際のタイプを(リフレクションを使用して)チェックし、呼び出すのに最適なメソッドを選択するコードを生成します。ここには、この1つのジェネリックメソッドしかないため、適切な型パラメーターで呼び出されます。
この例では、出力は次のように記述した場合と同じです。
foreach (var o in objects)
{
MethodInfo method = typeof(Service).GetMethod("Process");
MethodInfo generic = method.MakeGenericMethod(o.GetType());
generic.Invoke(service, new object[] { o });
}
動的タイプのバージョンは間違いなく短く、書くのが簡単です。また、この関数を複数回呼び出すパフォーマンスについても心配する必要はありません。同じタイプの引数を使用した次の呼び出しは、DLRのキャッシングメカニズムのおかげで高速になるはずです。もちろん、呼び出されたデリゲートをキャッシュするコードを記述できますが、dynamic
型を使用することで、この動作を無料で取得できます。
呼び出すジェネリックメソッドにパラメーター化された型の引数がない場合(その型パラメーターを推測できないため)、ジェネリックメソッドの呼び出しを次の例のようにヘルパーメソッドでラップできます。
class Program
{
static void Main(string[] args)
{
object obj = new Alpha();
Helper((dynamic)obj);
}
public static void Helper<T>(T obj)
{
GenericMethod<T>();
}
public static void GenericMethod<T>()
{
Console.WriteLine("GenericMethod<" + typeof(T) + ">");
}
}
型安全性の向上
dynamic
リフレクションAPIを使用する代わりにオブジェクトを使用することの本当に優れている点は、実行時までわからないこの特定の型のコンパイル時間チェックだけが失われることです。その他の引数とメソッドの名前は、通常どおりコンパイラーによって静的に分析されます。引数を削除または追加したり、型を変更したり、メソッド名を変更したりすると、コンパイル時エラーが発生します。これは、メソッド名を文字列Type.GetMethod
として、引数をオブジェクト配列として指定した場合は発生しませんMethodInfo.Invoke
。
以下は、コンパイル時(コメントコード)と実行時のその他のエラーを捕捉する方法を示す簡単な例です。また、DLRがどのメソッドを呼び出すかを解決しようとする方法も示しています。
interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }
class Program
{
static void Main(string[] args)
{
var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
for (int i = 0; i < objects.Length; i++)
{
ProcessItem((dynamic)objects[i], "test" + i, i);
//ProcesItm((dynamic)objects[i], "test" + i, i);
//compiler error: The name 'ProcesItm' does not
//exist in the current context
//ProcessItem((dynamic)objects[i], "test" + i);
//error: No overload for method 'ProcessItem' takes 2 arguments
}
}
static string ProcessItem<T>(T item, string text, int number)
where T : IItem
{
Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
typeof(T), text, number);
return "OK";
}
static void ProcessItem(BarItem item, string text, int number)
{
Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
}
}
ここで、引数をdynamic
型にキャストすることで、再度いくつかのメソッドを実行します。最初の引数の型の検証のみがランタイムに延期されます。呼び出すメソッドの名前が存在しない場合、または他の引数が無効な場合(引数の数が間違っているか、型が間違っている場合)、コンパイラエラーが発生します。
dynamic
引数をメソッドに渡すと、この呼び出しは最近バインドされます。メソッドのオーバーロードの解決は実行時に行われ、最適なオーバーロードを選択しようとします。そのため、型のProcessItem
オブジェクトを使用してメソッドを呼び出すと、BarItem
実際には非ジェネリックメソッドが呼び出されます。これは、この型に適しているためです。ただし、Alpha
このオブジェクトを処理できるメソッドがないため、型の引数を渡すとランタイムエラーが発生します(ジェネリックメソッドには制約がwhere T : IItem
あり、Alpha
クラスにはこのインターフェイスが実装されていません)。しかし、それがすべてのポイントです。コンパイラには、この呼び出しが有効であるという情報がありません。あなたはプログラマーとしてこれを知っており、このコードがエラーなしで実行されることを確認する必要があります。
戻り型の問題
あなたは、ダイナミック型のパラメータを持つ非空メソッドを呼び出している場合は、その戻り値の型は、おそらくだろうことがdynamic
、あまりにも。したがって、前の例をこのコードに変更する場合:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
その場合、結果オブジェクトのタイプはになりますdynamic
。これは、コンパイラが呼び出されるメソッドを常に認識しているわけではないためです。関数呼び出しの戻り値の型がわかっている場合は、それを必要な型に暗黙的に変換して、残りのコードが静的に型付けされるようにする必要があります。
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
タイプが一致しない場合、ランタイムエラーが発生します。
実際、前の例で結果値を取得しようとすると、2番目のループ反復でランタイムエラーが発生します。これは、void関数の戻り値を保存しようとしたためです。