Home / ぼやきごと / 2014-03-02
2014-03-02

C#: dynamic によるジェネリック型の動的オーバロード

今更 C# 4.0 のお話です。

C#で、 Execute というメソッドにある型の引数を渡した時、次のように動作して欲しいものとします。

  1. 型が string であれば string 版のオーバロードを呼ぶ。
  2. そうではなく型が IEnumerable<T>T はジェネリック型)であれば IEnumerable<T> 版のオーバロードを呼ぶ。
  3. 上記以外の場合は object 版のオーバロードを呼ぶ。

直接 Execute メソッドを呼び出すのであれば特に難しいことはありません。

コード
すべて開くすべて閉じる
  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
 
 
 
 
-
|
-
|
-
|
!
|
|
-
|
!
|
|
-
|
!
|
|
-
|
|
|
|
|
!
!
!
using System;
using System.Collections.Generic;
 
namespace sample
{
    class Program
    {
        private static void Execute(string value)
        {
            Console.WriteLine("string");
        }
 
        private static void Execute<T>(IEnumerable<T> values)
        {
            Console.WriteLine("IEnumerable<T> : T is " + typeof(T).Name);
        }
 
        private static void Execute(object value)
        {
            Console.WriteLine("object : " + value.GetType().Name);
        }
 
        static void Main(string[] args)
        {
            Execute(0);
            Execute(new Exception());
            Execute("abc");
            Execute(new List<int>());
            Execute(new char[1]);
        }
    }
}
出力
object : Int32
object : Exception
string
IEnumerable<T> : T is Int32
IEnumerable<T> : T is Char

しかし、引数に渡す型がジェネリック型である場合は話が違ってきます。

コード
すべて開くすべて閉じる
  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
 
 
 
 
-
|
-
-
!
|
|
|
|
-
-
!
!
|
|
-
|
|
|
|
|
!
!
!
using System;
using System.Collections.Generic;
 
namespace sample
{
    class Program
    {
        // 実装略(前述のコードと同じ)
        private static void Execute(string value) { /* ... */ }
        private static void Execute<T>(IEnumerable<T> values) { /* ... */ }
        private static void Execute(object value) { /* ... */ }
 
        private static void CallExecute<T>(T value)
        {
            // ジェネリック型 T の値に対してオーバロード解決を試みる
            Execute(value);
        }
 
        static void Main(string[] args)
        {
            CallExecute(0);
            CallExecute(new Exception());
            CallExecute("abc");
            CallExecute(new List<int>());
            CallExecute(new char[1]);
        }
    }
}
出力
object : Int32
object : Exception
object : String
object : List`1
object : Char[]

先述のコードと同じ結果を期待したのですが、引数の型に関わらず object 版のオーバロードが呼び出されてしまっています。
ジェネリック型 T に対するオーバロード解決はコンパイル時に行われ、型制約が指定されていない以上 object 版のオーバロードにしかマッチしないためです。

そこで、 Execute メソッドの引数を dynamic 型にキャストすることで、オーバロード解決を実行時に行うようにしてみます。

コード(変更部分のみ)
すべて開くすべて閉じる
 13
 14
 15
 16
 17
 
-
-
!
!
        private static void CallExecute<T>(T value)
        {
            // ジェネリック型 T の値に対して動的なオーバロード解決を試みる
            Execute((dynamic)value);
        }
出力
object : Int32
object : Exception
string
IEnumerable<T> : T is Int32
IEnumerable<T> : T is Char

見事に期待通りの動作をしてくれました。

このように、 dynamic 型を使うことで、ジェネリック型の引数に対して動的なオーバロード解決を行うことが出来ます。
もちろん実行時に解決するためのオーバヘッドはゼロではありませんが、 is 演算子で型を調べて if 文で分岐させたりリフレクションを利用したりするしかなかった C# 3.0 以前と比べれば相当マシになっていると言えるでしょう。

今回の内容は、C++のテンプレート型であればコンパイル時に行えることではありますが、そこは仕組みの違い上仕方ありません。

Category: [C#][プログラミング] - 2014-03-02 18:24:15