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とMyActionが異なる型だからです。
戻り値も引数も同じ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の型を変換することができます。