ぼやきごと/2014-03-02/C#: dynamic によるジェネリック型の動的オーバロード のバックアップソース(No.1)

#blog2navi()
*C#: dynamic によるジェネリック型の動的オーバロード [#s0b62411]

今更 C# 4.0 のお話です。

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

+型が @code{string}; であれば @code{string}; 版のオーバロードを呼ぶ。
+そうではなく型が @code{IEnumerable<T>}; (@code{T}; はジェネリック型)であれば @code{IEnumerable<T>}; 版のオーバロードを呼ぶ。
+上記以外の場合は @code{object}; 版のオーバロードを呼ぶ。

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

:コード|
#code(csharp){{
using System;
using System.Collections.Generic;

namespace sample
{
    class Program
    {
        private static void Execute<T>(IEnumerable<T> values)
        {
            Console.WriteLine("IEnumerable<T> : T is " + typeof(T).Name);
        }

        private static void Execute(string value)
        {
            Console.WriteLine("string");
        }

        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]);
        }
    }
}
}}
:出力|
#pre{{
object : Int32
object : Exception
string
IEnumerable<T> : T is Int32
IEnumerable<T> : T is Char
}}

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

:コード|
#code(csharp){{
using System;
using System.Collections.Generic;

namespace sample
{
    class Program
    {
        // 実装略(前述のコードと同じ)
        private static void Execute<T>(IEnumerable<T> values) { /* ... */ }
        private static void Execute(string value) { /* ... */ }
        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]);
        }
    }
}
}}
:出力|
#pre{{
object : Int32
object : Exception
object : String
object : List`1
object : Char[]
}}

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

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

:コード(変更部分のみ)|
#code(csharp,13-){{
        private static void CallExecute<T>(T value)
        {
            // ジェネリック型 T の値に対して動的なオーバロード解決を試みる
            Execute((dynamic)value);
        }
}}
:出力|
#pre{{
object : Int32
object : Exception
string
IEnumerable<T> : T is Int32
IEnumerable<T> : T is Char
}}

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

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

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

RIGHT:Category: &#x5b;[[C#>ぼやきごと/カテゴリ/C#]]&#x5d;&#x5b;[[プログラミング>ぼやきごと/カテゴリ/プログラミング]]&#x5d; - 2014-03-02 18:24:15
----
RIGHT:&blog2trackback();
#comment(above)
#blog2navi()