以非泛型方式调用泛型方法

通过泛型方法定义具有特定类型意义的方法是常用的手段。但在某些特定情况下,例如在一些通用的框架中,直到运行时才能确定泛型类型参数,就必须通过非泛型方式来调用泛型方法。
假定有这样一个方法:

public static void Add<T>(T obj, IList<T> list)
{
      list.Add(obj);
}

如果想换成这样调用:

Add(Type type, object obj, object list);

通常的方法是这样的:

void Add(Type type, object obj, object list)
{
    MethodInfo mi = typeof(MyType).GetMethod("Add");
    MethodInfo gmi = mi.MakeGenericMethod(type);
    gmi.Invoke(new object[] { obj, list });
}

当然,除了性能上的问题,这个方案是完全可行的。但是经过测试,通过这种包装后的耗时比直接的泛型调用相差将近1000倍。因此还需要一些折中一点的方案。为此,我请教了装配脑袋。他给出了一个非常好的方案:
先定义一个泛型包装委托:

public delegate void GM<T>(T obj, IList<T> list);

然后再定义一个非泛型包装的接口:

interface ING
{
    void NGM(object obj, object list);
}

然后再实现这个接口,在实现类中直接调用传入的泛型委托:

public class GClass<T> : ING
{
    private GM<T> m_gmd;

    public GClass(GM<T> gmd)
    {
        m_gmd = gmd;
    }

    INGClass 成员
}

然后就可以非常简单地使用已有的泛型方法来获得一个非泛型接口实现了:

static ING GetNGC(Type genericType, Type methodType, string methodName)
{
    MethodInfo mi = methodType.GetMethod(methodName);
    MethodInfo gmi = mi.MakeGenericMethod(genericType);
    Delegate gmd = Delegate.CreateDelegate(typeof(GM<>).MakeGenericType(genericType), gmi);
    return Activator.CreateInstance(typeof(GClass<>).MakeGenericType(genericType), gmd) as ING;
}

通过执行所返回接口的非泛型方法来达到调用泛型方法的目的:

ING ng = GetNGC(typeof(int), typeof(MyType), "Add");
ng.NGM(i, list);

比对一下,耗时大约是直接泛型调用耗时的三倍。显然这个方案是一个非常实用的方案。归纳一下,一共需要四步:

  • 定义泛型委托;
  • 定义非泛型接口;
  • 实现这个接口;
  • 通过泛型委托获取非泛型接口的实现。

其中前两步比较简单,后两部稍嫌麻烦。于是,我们再进一步实现一个通用的接口实现及其输出。

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace GenericMethodTest
{
    /// <summary>
    /// 接口生成器
    /// </summary>
    internal static class InterfaceGenerator
    {
        private static Random _Random = new Random();

        private static char GetRandomLetter()
        {
            int i = (_Random.Next() % 26) + 97;
            byte[] b = BitConverter.GetBytes(i);
            return BitConverter.ToChar(b, 0);
        }

        private static string GetRandomString(int n)
        {
            char[] chars = new char[n];
            for (int i = 0; i < n; i++)
            {
                chars[i] = GetRandomLetter();
            }
            return new string(chars);
        }

        private static void LoadArg(ILGenerator gen, int index)
        {
            switch (index)
            {
                case 0:
                    gen.Emit(OpCodes.Ldarg_0);
                    break;
                case 1:
                    gen.Emit(OpCodes.Ldarg_1);
                    break;
                case 2:
                    gen.Emit(OpCodes.Ldarg_2);
                    break;
                case 3:
                    gen.Emit(OpCodes.Ldarg_3);
                    break;
                default:
                    if (index < 128)
                    {
                        gen.Emit(OpCodes.Ldarg_S, index);
                    }
                    else
                    {
                        gen.Emit(OpCodes.Ldarg, index);
                    }
                    break;
            }
        }

        public static T GetInterface<T>(Delegate GM)
        {
            if (typeof(T).IsInterface)
            {
                Type delegateType = GM.GetType();
                if (delegateType.IsGenericType)
                {
                    if (typeof(MulticastDelegate).IsAssignableFrom(delegateType.GetGenericTypeDefinition()))
                    {
                        Type[] genericTypes = delegateType.GetGenericArguments();
                        if (genericTypes.Length == 1)
                        {
                            Type genericType = genericTypes[0];

#if SAVE
                            string theFilename = "InterfaceGenerator.Attachments.dll";
#endif
                            AssemblyName aname = new AssemblyName();
                            aname.Name = string.Format("InterfaceGenerator.Attachments.{0}", GetRandomString(16));
                            aname.Version = new Version("2.0.0.0");
                            AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(aname,
#if SAVE
 AssemblyBuilderAccess.RunAndSave
#else
 AssemblyBuilderAccess.Run
#endif
);
                            ModuleBuilder module = assembly.DefineDynamicModule(GetRandomString(8)
#if SAVE
, theFilename
#endif
);
                            TypeBuilder builder = module.DefineType(GetRandomString(16), TypeAttributes.Sealed | TypeAttributes.Class | TypeAttributes.Public);
                            builder.AddInterfaceImplementation(typeof(T));

                            // 先定义成员域,用于保存传入的委托。
                            FieldBuilder field = builder.DefineField(GetRandomString(8), delegateType, FieldAttributes.Private);

                            // 定义构造器。
                            ConstructorBuilder ctor = builder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { delegateType });
                            ILGenerator ctorGen = ctor.GetILGenerator();
                            ctorGen.Emit(OpCodes.Ldarg_0);
                            ctorGen.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[] { }));
                            ctorGen.Emit(OpCodes.Ldarg_0);
                            ctorGen.Emit(OpCodes.Ldarg_1);
                            ctorGen.Emit(OpCodes.Stfld, field);
                            ctorGen.Emit(OpCodes.Ret);

                            // 虽然这么写,但事实上接口只有一个方法。
                            foreach (MethodInfo bmi in typeof(T).GetMethods())
                            {
                                ParameterInfo[] paramInfos = bmi.GetParameters();
                                Type[] argTypes = new Type[paramInfos.Length];
                                int i = 0;
                                foreach (ParameterInfo pi in paramInfos)
                                {
                                    argTypes[i++] = pi.ParameterType;
                                }
                                MethodAttributes attributes = MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.ReuseSlot | MethodAttributes.Public;
                                MethodBuilder method = builder.DefineMethod(bmi.Name, attributes, bmi.ReturnType, argTypes);
                                builder.DefineMethodOverride(method, bmi);
                                MethodInfo dmi = delegateType.GetMethod("Invoke");
                                ILGenerator methodGen = method.GetILGenerator();
                                bool hasReturn = false;
                                if (dmi.ReturnType != typeof(void))
                                {
                                    methodGen.DeclareLocal(dmi.ReturnType);
                                    hasReturn = true;
                                }
                                methodGen.Emit(OpCodes.Ldarg_0);
                                methodGen.Emit(OpCodes.Ldfld, field);

                                i = 0;
                                foreach (ParameterInfo pi in dmi.GetParameters())
                                {
                                    LoadArg(methodGen, i + 1);
                                    if (!pi.ParameterType.IsAssignableFrom(argTypes[i]))
                                    {
                                        if (argTypes[i].IsClass)
                                        {
                                            methodGen.Emit(OpCodes.Castclass, pi.ParameterType);
                                        }
                                        else
                                        {
                                            methodGen.Emit(OpCodes.Unbox, pi.ParameterType);
                                        }
                                    }
                                    i++;
                                }
                                methodGen.Emit(OpCodes.Callvirt, dmi);
                                if (hasReturn)
                                {
                                    methodGen.Emit(OpCodes.Stloc_0);
                                    methodGen.Emit(OpCodes.Ldloc_0);
                                }
                                methodGen.Emit(OpCodes.Ret);
                            }
                            Type target = builder.CreateType();
#if SAVE
                            assembly.Save(theFilename);
#endif
                            ConstructorInfo ci = target.GetConstructor(new Type[] { delegateType });
                            return (T) ci.Invoke(new object[] { GM });
                        }
                    }
                }
            }
            return default(T);
        }
    }
}

结论:
以下是测试代码:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace GenericMethodTest
{
    // 为泛型方法定义的委托
    public delegate void GM<T>(T obj, IList<T> list);

    // 为非泛型方法定义的接口
    public interface ING
    {
        void NGM(object obj, object list);
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<int> list = new List<int>();
            System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
            watch.Reset();
            watch.Start();
            for (int i = 0; i < 1000000; i++)
            {
                list.Add(i);
            }
            watch.Stop();
            long l1 = watch.ElapsedMilliseconds;
            watch.Reset();
            watch.Start();
            GM<int> gm = new GM<int>(Program.Add);
            for (int i = 0; i < 1000000; i++)
            {
                gm(i, list);
            }
            watch.Stop();
            long l2 = watch.ElapsedMilliseconds;
            watch.Reset();
            watch.Start();
            MethodInfo mi = typeof(Program).GetMethod("Add");
            MethodInfo gmi = mi.MakeGenericMethod(typeof(int));
            for (int i = 0; i < 1000000; i++)
            {
                gmi.Invoke(null, new object[] { i, list });
            }
            watch.Stop();
            long l3 = watch.ElapsedMilliseconds;
            watch.Reset();
            watch.Start();
            ING ng1 = GetNGC(typeof(int), typeof(Program), "Add");
            for (int i = 0; i < 1000000; i++)
            {
                ng1.NGM(i, list);
            }
            watch.Stop();
            long l4 = watch.ElapsedMilliseconds;
            watch.Reset();
            watch.Start();
            ING ng2 = InterfaceGenerator.GetInterface<ING>(new GM<int>(Program.Add));
            for (int i = 0; i < 1000000; i++)
            {
                ng2.NGM(i, list);
            }
            watch.Stop();
            long l5 = watch.ElapsedMilliseconds;
            Console.WriteLine("{0}\n{1} vs {2} vs {3} vs {4} vs {5}", list.Count, l1, l2, l3, l4, l5);
            Console.ReadLine();
        }

        public static void Add<T>(T obj, IList<T> list)
        {
            list.Add(obj);
        }

        static ING GetNGC(Type genericType, Type methodType, string methodName)
        {
            MethodInfo mi = methodType.GetMethod(methodName);
            MethodInfo gmi = mi.MakeGenericMethod(genericType);
            Delegate gmd = Delegate.CreateDelegate(typeof(GM<>).MakeGenericType(genericType), gmi);
            return Activator.CreateInstance(typeof(GClass<>).MakeGenericType(genericType), gmd) as ING;
        }
    }

    public class GClass<T> : ING
    {
        private GM<T> m_gmd;

        public GClass(GM<T> gmd)
        {
            m_gmd = gmd;
        }

        INGClass 成员
    }
}

测试结果:

方案 耗时 比对 其他优点
直接调用 18 1 不通用
泛型委托包装 43 2.39 不通用
反射 16538 918.78 通用,不需额外定义
非泛型接口包装 60 3.33 通用,需要额外定义并实现
动态生成的非泛型接口包装 72 4 通用,需要额外定义

转发:https://www.cnblogs.com/Barton131420/archive/2007/02/07/643026.html

猜你喜欢

转载自blog.csdn.net/weixin_42523286/article/details/88171971