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

出处:http://www.cnblogs.com/Barton131420/archive/2007/02/07/643026.html

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

posted on 2007-02-07 10:13 双鱼座 阅读(2866) 评论(20)  编辑  收藏

评论

 

使用delegate是使用反射的最常用方法,因为可以大大提高效率。不过要使用delegate的话必须每个delegate绑定“每个实例的某个方法”(除非静态方法),这样的话在很多情况下不可行。
所以一般都是保留一个MethodInfo,然后以后每次就可以直接Invoke。其实只要不把它放在大量循环里性能还是可以接受的,呵呵。   回复  引用  查看    

#2楼  2007-02-07 12:08 装配脑袋

MethodInfo的获取要远比Inovke它的速度快,所以其实缓存MethodInfo实例的意义不大。用Helper方法解除Delegate与object的绑定是一个不错的思路。   回复  引用  查看    

#3楼  2007-02-07 13:02 xiaotie

仔细看了一下,后4种调用方式都需要指定类型,为何不直接调用呢?让一切工作交给CLI去做,也是运行时绑定。
如:
Add("haha", new List<String>());
  回复  引用  查看    

#4楼  2007-02-07 13:03 装配脑袋

@xiaotie
可以阿,后台从参数.GetType()就行了。   回复  引用  查看    

#5楼 [楼主] 2007-02-07 13:19 双鱼座

@Jeffrey Zhao
你的意思使用反射虽然性能有所降低,但可以接受,对吧?不过不一定所有的场合都能接受,毕竟差别太大。如果有代价不大且性能更好的方式,何乐而不为呢?这正是本文的初衷。
@xiaotie
当然是需要指定类型,否则连泛型都没有必要了。问题是:这个泛型参数是运行时才能指定而不是设计时,所以不能直接调用泛型方法,而必须通过变量来指定泛型类型然后再调用。   回复  引用  查看    

#6楼  2007-02-07 13:28 xiaotie

@双鱼座

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

方法已经是运行时推演的了,也就是说,不必指定具体的对象类型,这样调用:
IList<int> list = new List<int>();
Add(1,list);

调用的是Add<int>(...)方法

IList<String> list = new List<String>();
Add("",list);

调用的是Add<String>方法

所以,传入不同类型,运行时自动产生并且调用相关方法了,并不需要反射来强行指定。只是,传入的类型参数之间必须遵守约束条件,就是假设第一个参数是T,第二个参数必须是IList<T>对象。   回复  引用  查看    

#7楼  2007-02-07 14:10 装配脑袋

@xiaotie

你举这一例子是编译时推算的,并非运行时推算。如果泛型有运行时推算和选择的话(最好是JIT时)那么应用还能扩大好几倍。   回复  引用  查看    

#8楼  2007-02-07 15:09 xiaotie

@装配脑袋
我弄错了。不过觉得这个解决方案看起来不简洁并且感觉也没完全解决问题。   回复  引用  查看    

#9楼 [TrackBack] 2007-02-07 19:55 xiaotie

对 Kanas.Net 的《以非泛型方式调用泛型方法 》一文的探讨
[引用提示]xiaotie引用了该文章, 地址: http://www.cnblogs.com/xiaotie/archive/2007/02/07/643947.html   回复  引用  查看    

#10楼 [楼主] 2007-02-07 20:33 双鱼座

@xiaotie
非常高兴你对这个问题的关注!
个人认为,“最好不要以非范型方式调用范型方法”这个结论非常正确。泛型方法有太多的优势,我们都非常清楚。但如果将结论改成“绝对不要以非范型方式调用范型方法”我一定不会答应。给你介绍一下我的应用场景。我的框架是通用的,事先无法预知应用程序会包含哪些业务类型。例如,在我的框架中,你可以这样:
IList<Contact> = context.Select<Contact>(true);
表示装入所有的联系人,参数true表示同时装入联系人相关的集合,例如Calls和Ticklers,因为框架分析得知,Contact类型有两个集合属性:IList<Call>类型的Calls和IList<TIckler>类型的Ticklers。框架在执行完装入Contact后, 立即执行Select<Call>(false)和Select<Tickler>(false)。Select<Contact>是用户发动的,当然可以直接调用框架提供的泛型方法。那么框架所装入的Select<Call>和Select<Tickler>是框架发动的,就无法直接调用泛型方法了!
用非泛型方式调用泛型方法,从哪儿看都是别别扭扭的。但是在一些特定的场合的确没有更好的选择。
你的改良在于剔除了委托的概念,这是Java开发者最通常的思路,因为Java中没有委托。但是在这个问题上,泛型委托比直接在Helper类中定义泛型方法更有价值:因为委托可以很轻易地攫取任何类的任何相同原型的方法,并且不经修改就可以用于异步!
除了委托以外,你的方案与我原文中的方案4(也就是装配脑袋的非泛型接口包装方案)没有什么本质区别。而我原文中的方案5其实就是方案4的Emit化,解决了所有泛型方法原型的实现而不仅仅是文中列举的某一个具体方法。换句话说,只要有一个泛型委托和一个对应的非泛型接口,所有的问题都解决了。而不象方案4,每增加一个泛型方法就必须重复写一个实现类。
当然,方案5还存在一个问题,就是没有解决多泛型参数的问题。稍微展开一下也是可以实现的。   回复  引用  查看    

#11楼  2007-02-07 21:56 臭石头

看了N遍,总算有点一知半解了。

看到反射的性能差别那么大,真想鄙视MS,MS也该好好优化优化这个反射了,大家都知道反射是非常好的东西,但一千倍的性能差别,也太夸张了。既然用委托可以解决调用泛型方法的问题,MS至少应该对这方面的委托进行优化。

  回复  引用  查看    

#12楼 [楼主] 2007-02-08 09:07 双鱼座

@臭石头
这个倒不是MS的问题。与Java相比,C#开发者够幸福的了。
Invoke的执行性能以及Activator的执行性能比较差,MS有文档说明的。这些反射不仅考虑本地还要考虑跨进程、甚至跨主机的执行。   回复  引用  查看    

#13楼  2007-02-08 09:16 臭石头

我知道反射要做的事情非常多,我只是想说,MS完全可以在反射内部,针对某些情况进行优化,使得这些部分,性能差别不是那么大。

如果反射性能和直接执行差别在10倍以上的,我想,应该没有人不乐意去使用吧。我不熟悉Java,不知大Java的反射如果,只是我一个朋友说似乎还不错吧   回复  引用  查看    

#14楼  2007-02-08 19:01 xiaotie

@双鱼座
方案5所未解决的那个问题其实正式它所正要解决的问题,要解决这个问题,还需要再度应用方案4的方法,所以方案5我觉得没解决问题。而方案4,我觉得又太复杂了点,需要简化。个人觉得这里没必要使用委托使事情复杂化,保持类型的层次化和清晰化更重要。委托虽然比接口具备更强大的织出能力,但它的约束能力不如接口。又用委托又用接口,是一种设计上的重复。
对于你的场景,应该可以从中提取出一个接口来约束传入的类型、行为,或者采用event通知客户端发生某种事情。如果实在不能抽象出接口或者event来约束,那么应该把这个责任推给调用者。   回复  引用  查看    

#15楼 [楼主] 2007-02-08 20:45 双鱼座

@xiaotie
看来你比我想象的要固执一些。我没有看出来方案4解决了哪些问题而方案5没有解决(也许你需要更进一步了解一下方案5,事实上我最终采用的正是方案5)。委托使事情复杂了一点点,换来的是调用者方便一点点。另外,委托可以实现非侵入式,换句话说,可以解决跨程序集的问题,恐怕这一点是接口抽象的硬伤。
关于设计上的重复性,有些时候设计上的重复是有意义的,例如IComparable接口和Comparison委托,重复的意义也是换来了更多的方便性。更何况在本例中,一个是泛型,一个是非泛型。
在我的场景中,event恐怕无法解决这个问题。当然,更不能将责任推给调用者,这也是为了内聚的需要。   回复  引用  查看    

#16楼 [TrackBack] 2007-02-28 23:36 MonkRui

看了Kanas.Net的
[引用提示]MonkRui引用了该文章, 地址: http://www.cnblogs.com/RuiLei/archive/2007/02/28/660301.html   回复  引用  查看    

#17楼 [TrackBack] 2007-03-22 17:06 Teddy's Knowledge Base

本文针对双鱼座同志的以非泛型方式调用泛型方法一文,提出一种更通用的以非泛型方式调用泛型方法的实现——基于DynamicMethod的实现。
[引用提示]Teddy's Knowledge Base引用了该文章, 地址: http://www.cnblogs.com/teddyma/archive/2007/03/22/684306.html   回复  引用  查看    

#18楼 [TrackBack] 2007-03-29 18:10 erich.zhou

改进的
[引用提示]erich.zhou引用了该文章, 地址: http://www.cnblogs.com/erichzhou/archive/2007/03/29/693133.html   回复  引用  查看    

#19楼  2007-07-17 15:09 freeliver54

niu   回复  引用  查看    

#20楼 [TrackBack] 2007-07-21 00:02 小春

我们在项目中经常用哪种方式来反射方法来创建实例,调用指定的方法和属性?它们的性能怎么样?本文收藏了国外上面一些优秀的文章,仅供参考。
[引用提示]小春引用了该文章, 地址: http://www.cnblogs.com/cnzc/archive/2007/07/21/826093.html   回复  引用  查看    

  #1楼  2007-02-07 11:05 Jeffrey Zhao

你可能感兴趣的:(框架,object,list,String,Random,attributes)