delegateの型変換
C#では次のcodeはcompile errorになります。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Sample { class Program { public delegate void MyAction<T>(T arg); static void Main(string[] args) { Action<int> action = (x) => Console.WriteLine(x); MyAction<int> my_action = action; // error CS0029(Visual Studio 2008) my_action(1); } } }
これはAction
戻り値も引数も同じdelegateであるにも関わらず、型が違うというだけで上記のようなことはできません。
しかし、型が違ってもdelegateを呼び出す仕組み自体は変わらないわけですから、上記のようなことができて欲しいと思います。
(CやC++に慣れているせいか、なぜこれがC#で許されていないのかとても疑問です)
C#4.0ではcovarianceとcontravarianceが導入されるようですが、相変わらず異なる型のdelegateへの代入は許されていないようです。
これを解決するためには次のようなcodeを書いて、delegateの型を変換する必要があります。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; namespace Sample { class Program { public delegate void MyAction<TArg>(TArg arg); static void Main(string[] args) { try { Action<int> action = (x) => Console.WriteLine(x); MyAction<int> my_action = Convert<MyAction<int>, Action<int>>(action); my_action(1); } catch (Exception ex) { Console.WriteLine(ex); } } public static TActionRet Convert<TActionRet, TAction>(TAction action) where TActionRet : class where TAction : class { // 戻り値および引数の型チェック if (!ValidateDelegateSignature(typeof(TActionRet), typeof(TAction))) { throw new ArgumentException("typeof \"{0}\" and \"{1}\" is not compatible."); } Delegate d = action as Delegate; if (null != d) { // delegateを作成 return Delegate.CreateDelegate(typeof(TActionRet), d.Method) as TActionRet; } throw new ArgumentException("parameter \"action\" is not subclass of Delegate."); } public static bool ValidateDelegateSignature(Type d1, Type d2) { MethodInfo m1 = d1.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); MethodInfo m2 = d2.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); if ((null == m1) || (null == m2)) { return false; } if (m1.ReturnType != m2.ReturnType) { return false; } ParameterInfo[] pis1 = m1.GetParameters(); ParameterInfo[] pis2 = m2.GetParameters(); if (pis1.Length != pis2.Length) { return false; } for (int i = 0; i < pis1.Length; ++i) { if (pis1[i].ParameterType != pis2[i].ParameterType) { return false; } } return true; } } }
このような方法を用いることでdelegateの型を変換することができます。