使用高效的 .net 反射发出

本文通过一系列测试对比了直接调用、代理调用、反射Invoke、代理调用反射发出的泛型方法及DynamicMethod的效率。结果显示,DynamicMethod通常表现最优,而反射Invoke效率最低。当参数数组长度增加时,直接调用的效率下降显著,而DynamicMethod和代理调用反射发出的方法保持高效。
摘要由CSDN通过智能技术生成

本文概述

测试结果分析

测试结果

测试代码

概述

对于vs.net 反射发出动态方法、动态类型等,原来一直认为虽然有用,但效率不如常规方法编译的,因此一直没有深入研究,直到前些天才发现,如果用法适当,其运行效率不比常规方法创建的类、方法差,甚至更高。

 我是根据MSDN中的一个示例为基础做的测试,其位置位于:

MSDN
  开发工具和语言
    Visual Studio 文档
      Visual Studio中的.net Framework编程
        .net Framework高级开发
          发出动态方法和程序集
            使用反射发出
              如何:用反射发出定义泛型方法

测试结果分析 

在测试中,共对5种调用方法进行了测试:

  1. 直接调用类的泛型静态方法
  2. 用代理调用类的泛型静态方法
  3. 用 Invoke 调用反射发出定义的新类型的泛型方法
  4. 用代理调用反射发出定义的新类型的泛型方法
  5. 用DynamicMethod调用反射发出定义的新类型的泛型方法

从测试结果可以发现一些很有趣的现象,这也与我事先预期不同:

  • 直接调用当前工程中类的方法,并不是最快的,其效率低于使用代理方式,特别是当循环10000次,参数数组长度为100时,用时相差将近一半;
  • 用 MethodInfo 的 Invoke 方法调用(上述第3种调用方式),应该是效率最低的,测试结果也显示,其比后面的4和5两种方式效率要低将近一倍,但测试结果同时显示了,当循环测试次数为10000次时,用 Invoke 方式调用反射发出定义的新类型的泛型方法(MethodInfo对象),其用时比直接调用类的静态方法还要少,在我想来,这中奇怪的结果,应该是用正常方式写的类的方法,被编译成中间代码后,比针对性的使用反射发出生成的中间代码效率低

用反射发出定义的新类型的泛型方法,获得其 MethodInfo 后,用 Invoke 方式调用效率最低,如果有合适签名的代理,则用代理调用效率最高;如果没合适签名的代理,则可以定义一个通用类型代理,形如:

public   delegate   object  FastInvokeHandler( object  target,  object [] paramters);

然后再用反射发出定义一个同该代理签名的 DynamicMethod 方法,在该方法中,将参数相关参数都压入堆栈,再调用原来的方法,之后再处理 ref 参数和返回值,完成 DynamicMethod,然后创建该动态方法的代理,调用时用该代理调用,其效率与直接调用工程内方法差不多。

 

测试结果


=============================================
测试开始,每个测试运行10000次,参数数组长度=11
---------------------------------------------
测1:直接调用类的 Factory 方法:运行时间:67
测2:用代理直接调用类的 Factory 方法:运行时间:65
测3:用 Invoke 调用以反射发出在动态程序集中定义的泛型方法:运行时间:52
测4:用代理调用 以反射发出在动态程序集中定义的泛型方法:运行时间:25
测5:DynamicMethod调用反射发出在动态程序集中定义的泛型方法:运行时间:25
=============================================

=============================================
测试开始,每个测试运行10000次,参数数组长度=11
---------------------------------------------
测1:直接调用类的 Factory 方法:运行时间:66
测2:用代理直接调用类的 Factory 方法:运行时间:67
测3:用 Invoke 调用以反射发出在动态程序集中定义的泛型方法:运行时间:65
测4:用代理调用 以反射发出在动态程序集中定义的泛型方法:运行时间:24
测5:DynamicMethod调用反射发出在动态程序集中定义的泛型方法:运行时间:25
=============================================

=============================================
测试开始,每个测试运行10000次,参数数组长度=100
---------------------------------------------
测1:直接调用类的 Factory 方法:运行时间:127
测2:用代理直接调用类的 Factory 方法:运行时间:69
测3:用 Invoke 调用以反射发出在动态程序集中定义的泛型方法:运行时间:77
测4:用代理调用 以反射发出在动态程序集中定义的泛型方法:运行时间:46
测5:DynamicMethod调用反射发出在动态程序集中定义的泛型方法:运行时间:45
=============================================

=============================================
测试开始,每个测试运行10000次,参数数组长度=100
---------------------------------------------
测1:直接调用类的 Factory 方法:运行时间:126
测2:用代理直接调用类的 Factory 方法:运行时间:59
测3:用 Invoke 调用以反射发出在动态程序集中定义的泛型方法:运行时间:72
测4:用代理调用 以反射发出在动态程序集中定义的泛型方法:运行时间:44
测5:DynamicMethod调用反射发出在动态程序集中定义的泛型方法:运行时间:46
=============================================

=============================================
测试开始,每个测试运行100000次,参数数组长度=11
---------------------------------------------
测1:直接调用类的 Factory 方法:运行时间:342
测2:用代理直接调用类的 Factory 方法:运行时间:242
测3:用 Invoke 调用以反射发出在动态程序集中定义的泛型方法:运行时间:513
测4:用代理调用 以反射发出在动态程序集中定义的泛型方法:运行时间:245
测5:DynamicMethod调用反射发出在动态程序集中定义的泛型方法:运行时间:245
=============================================

=============================================
测试开始,每个测试运行100000次,参数数组长度=11
---------------------------------------------
测1:直接调用类的 Factory 方法:运行时间:339
测2:用代理直接调用类的 Factory 方法:运行时间:241
测3:用 Invoke 调用以反射发出在动态程序集中定义的泛型方法:运行时间:517
测4:用代理调用 以反射发出在动态程序集中定义的泛型方法:运行时间:237
测5:DynamicMethod调用反射发出在动态程序集中定义的泛型方法:运行时间:242
=============================================

=============================================
测试开始,每个测试运行100000次,参数数组长度=100
---------------------------------------------
测1:直接调用类的 Factory 方法:运行时间:568
测2:用代理直接调用类的 Factory 方法:运行时间:481
测3:用 Invoke 调用以反射发出在动态程序集中定义的泛型方法:运行时间:719
测4:用代理调用 以反射发出在动态程序集中定义的泛型方法:运行时间:455
测5:DynamicMethod调用反射发出在动态程序集中定义的泛型方法:运行时间:459
=============================================

=============================================
测试开始,每个测试运行100000次,参数数组长度=100
---------------------------------------------
测1:直接调用类的 Factory 方法:运行时间:564
测2:用代理直接调用类的 Factory 方法:运行时间:485
测3:用 Invoke 调用以反射发出在动态程序集中定义的泛型方法:运行时间:730
测4:用代理调用 以反射发出在动态程序集中定义的泛型方法:运行时间:462
测5:DynamicMethod调用反射发出在动态程序集中定义的泛型方法:运行时间:470
=============================================

=============================================
测试开始,每个测试运行100000次,参数数组长度=100
---------------------------------------------
测1:直接调用类的 Factory 方法:运行时间:588
测2:用代理直接调用类的 Factory 方法:运行时间:495
测3:用 Invoke 调用以反射发出在动态程序集中定义的泛型方法:运行时间:747
测4:用代理调用 以反射发出在动态程序集中定义的泛型方法:运行时间:462
测5:DynamicMethod调用反射发出在动态程序集中定义的泛型方法:运行时间:472
=============================================

 

测试代码

 

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

//  Declare a generic delegate that can be used to execute the 
//  finished method.
//
public   delegate  TOut D < TIn, TOut > (TIn[] input);

class  GenericMethodBuilder
{
    
//  This method shows how to declare, in Visual Basic, the generic
    
//  method this program emits. The method has two type parameters,
    
//  TInput and TOutput, the second of which must be a reference type
    
//  (class), must have a parameterless constructor (new()), and must
    
//  implement ICollection<TInput>. This interface constraint
    
//  ensures that ICollection<TInput>.Add can be used to add
    
//  elements to the TOutput object the method creates. The method 
    
//  has one formal parameter, input, which is an array of TInput. 
    
//  The elements of this array are copied to the new TOutput.
    
//
     public   static  TOutput Factory < TInput, TOutput > (TInput[] tarray)
        
where  TOutput :  class , ICollection < TInput > new ()
    {
        TOutput ret 
=   new  TOutput();
        ICollection
< TInput >  ic  =  ret;

        
foreach  (TInput t  in  tarray)
        {
            ic.Add(t);
        }
        
return  ret;
    }
    
public   static   void  loopTest()
    {
        
//  用反射发出定义一个动态程序集及泛型方法
        MethodInfo bound  =  getMyGenericMethod();

        
//  Display a string representing the bound method.
        Console.WriteLine(bound);

        D
< string , List < string >>  testByDelegate  =  (D < string , List < string >> )Delegate.CreateDelegate( typeof (D < string , List < string >> ), bound);

        D
< string , List < string >>  testByDynamicMethod  =  getDelegateByDynamicMethod < string , List < string >> (bound);

        D
< string , List < string >>  testByDirectDelegate  =   new  D < string , List < string >> (Factory < string , List < string >> );

        
string  yn  =   " Y " ;
        
while  (yn == " Y " )
        {
            testOnce(bound,testByDelegate,testByDynamicMethod,testByDirectDelegate);
            Console.WriteLine(
" 继续Y/结束N? " );
            yn 
=  Console.ReadLine();
            yn 
=  yn.ToUpper();
        }
    }
    
private   static   void  testOnce(MethodInfo bound, D < string , List < string >>  testByDelegate, D < string , List < string >>  testByDynamicMethod, D < string , List < string >>  testByDirectDelegate)
    {

        
//  准备测试
         string [] para  = " a " " b " " c " " d " " e " " f " " g " " h " " i " " j " " k "  };
        
string [] para1  =   new   string [ 100 ];

        
for  ( int  i  =   0 ; i  <  para1.Length; i ++ )
            para1[i] 
=   " a_ "   +  i.ToString();
        
int  times  =   0 ;
        Console.WriteLine(
" 请输入循环次数,然后按回车 " );
        
string  read  =  Console.ReadLine();
        
try
        {
            times 
=   int .Parse(read);
        }
        
catch
        {
            times 
=   100000 ;
        }
        Console.WriteLine(
" 使用小数组(X)还是较大数组(D)?,请输入 X 或 D " );
        read 
=  Console.ReadLine();
        
if  (read.ToLower()  ==   " d " )
            para 
=  para1;

        
int  testNum  =   0 ;
        Console.WriteLine(
" ============================================= " );
        Console.WriteLine(
" 测试开始,每个测试运行{0}次,参数数组长度={1} --------------------------------------------- " , times,para.Length);
        
        
string  method;
        Stopwatch watch;
        List
< string >  retVal;

        
//  测试1
        method  =   " 直接调用类的 Factory 方法 " ;
        watch 
=   new  Stopwatch();
        watch.Start();
        
for  ( int  i  =   0 ; i  <  times; i ++ )
        {
            retVal 
=  Factory < string , List < string >> (para);
        }
        watch.Stop();
        testNum
++ ;
        Console.WriteLine(
" 测{0}:{1}:运行时间:{2} " , testNum, method, watch.ElapsedMilliseconds);

        
//  测试2
        method  =   " 用代理直接调用类的 Factory 方法 " ;
        watch 
=   new  Stopwatch();
        watch.Start();
        
for  ( int  i  =   0 ; i  <  times; i ++ )
        {
            retVal 
=  testByDirectDelegate(para);
        }
        watch.Stop();
        testNum
++ ;
        Console.WriteLine(
" 测{0}:{1}:运行时间:{2} " , testNum, method, watch.ElapsedMilliseconds);


        
//  测试3
        method  =   " 用 Invoke 调用以反射发出在动态程序集中定义的泛型方法 " ;
        watch 
=   new  Stopwatch();
        watch.Start();
        
object [] paras  =   new   object [] { para };
        
for  ( int  i  =   0 ; i  <  times; i ++ )
        {
            retVal 
=  (List < string > )bound.Invoke( null , paras);
        }
        watch.Stop();
        testNum
++ ;
        Console.WriteLine(
" 测{0}:{1}:运行时间:{2} " , testNum, method, watch.ElapsedMilliseconds);


        
//  测试4
        method  =   " 用代理调用 以反射发出在动态程序集中定义的泛型方法 " ;
        watch 
=   new  Stopwatch();
        watch.Start();
        
for  ( int  i  =   0 ; i  <  times; i ++ )
        {
            retVal 
=  testByDelegate(para);
        }
        watch.Stop();
        testNum
++ ;
        Console.WriteLine(
" 测{0}:{1}:运行时间:{2} " , testNum, method, watch.ElapsedMilliseconds);


        
//  测试5
        method  =   " DynamicMethod调用反射发出在动态程序集中定义的泛型方法 " ;
        watch 
=   new  Stopwatch();
        watch.Start();
        
for  ( int  i  =   0 ; i  <  times; i ++ )
        {
            retVal 
=  testByDynamicMethod(para);
        }
        watch.Stop();
        testNum
++ ;
        Console.WriteLine(
" 测{0}:{1}:运行时间:{2} " , testNum, method, watch.ElapsedMilliseconds);
        Console.WriteLine(
" ============================================= " );
    }
    
private   static  D < TInput, TOutput >  getDelegateByDynamicMethod < TInput, TOutput > (MethodInfo bound)
            
where  TOutput :  class , ICollection < TInput > new ()
    {
        Type tInp 
=   typeof (TInput);
        Type atInp 
=  tInp.MakeArrayType();
        Type tOut 
=   typeof (TOutput);
        DynamicMethod handle 
=   new  DynamicMethod( " DynamicFactory " typeof (TOutput),  new  Type[] {  typeof (TInput).MakeArrayType() }, ( typeof (GenericMethodBuilder)).Module);
        ILGenerator ilg 
=  handle.GetILGenerator();
        ilg.Emit(OpCodes.Ldarg_0);
        ilg.EmitCall(OpCodes.Call, bound, 
null );
        ilg.Emit(OpCodes.Ret);
        D
< TInput, TOutput >  ret  =  (D < TInput, TOutput > )handle.CreateDelegate( typeof (D < TInput, TOutput > ));
        
return  ret;
    }
    
private   static  MethodInfo getMyGenericMethod()
    {
        
//  The following shows the usage syntax of the C#
        
//  version of the generic method emitted by this program.
        
//  Note that the generic parameters must be specified 
        
//  explicitly, because the compiler does not have enough 
        
//  context to infer the type of TOutput. In this case, TOutput
        
//  is a generic List containing strings.
        
//  
         string [] arr  =  {  " a " " b " " c " " d " " e "  };
        List
< string >  list1  =
            GenericMethodBuilder.Factory
< string , List < string >> (arr);
        Console.WriteLine(
" The first element is: {0} " , list1[ 0 ]);


        
//  Creating a dynamic assembly requires an AssemblyName
        
//  object, and the current application domain.
        
//
        AssemblyName asmName  =   new  AssemblyName( " DemoMethodBuilder1 " );
        AppDomain domain 
=  AppDomain.CurrentDomain;
        AssemblyBuilder demoAssembly 
=
            domain.DefineDynamicAssembly(asmName,
                AssemblyBuilderAccess.RunAndSave);

        
//  Define the module that contains the code. For an 
        
//  assembly with one module, the module name is the 
        
//  assembly name plus a file extension.
        ModuleBuilder demoModule  =
            demoAssembly.DefineDynamicModule(asmName.Name,
                asmName.Name 
+   " .dll " );

        
//  Define a type to contain the method.
        TypeBuilder demoType  =
            demoModule.DefineType(
" DemoType " , TypeAttributes.Public);

        
//  Define a public static method with standard calling
        
//  conventions. Do not specify the parameter types or the
        
//  return type, because type parameters will be used for 
        
//  those types, and the type parameters have not been
        
//  defined yet.
        
//
        MethodBuilder factory  =
            demoType.DefineMethod(
" Factory " ,
                MethodAttributes.Public 
|  MethodAttributes.Static);

        
//  Defining generic type parameters for the method makes it a
        
//  generic method. To make the code easier to read, each
        
//  type parameter is copied to a variable of the same name.
        
//
         string [] typeParameterNames  =  {  " TInput " " TOutput "  };
        GenericTypeParameterBuilder[] typeParameters 
=
            factory.DefineGenericParameters(typeParameterNames);

        GenericTypeParameterBuilder TInput 
=  typeParameters[ 0 ];
        GenericTypeParameterBuilder TOutput 
=  typeParameters[ 1 ];

        
//  Add special constraints.
        
//  The type parameter TOutput is constrained to be a reference
        
//  type, and to have a parameterless constructor. This ensures
        
//  that the Factory method can create the collection type.
        
//  
        TOutput.SetGenericParameterAttributes(
            GenericParameterAttributes.ReferenceTypeConstraint 
|
            GenericParameterAttributes.DefaultConstructorConstraint);

        
//  Add interface and base type constraints.
        
//  The type parameter TOutput is constrained to types that
        
//  implement the ICollection<T> interface, to ensure that
        
//  they have an Add method that can be used to add elements.
        
//
        
//  To create the constraint, first use MakeGenericType to bind 
        
//  the type parameter TInput to the ICollection<T> interface,
        
//  returning the type ICollection<TInput>, then pass
        
//  the newly created type to the SetInterfaceConstraints
        
//  method. The constraints must be passed as an array, even if
        
//  there is only one interface.
        
//
        Type icoll  =   typeof (ICollection <> );
        Type icollOfTInput 
=  icoll.MakeGenericType(TInput);
        Type[] constraints 
=  { icollOfTInput };
        TOutput.SetInterfaceConstraints(constraints);

        
//  Set parameter types for the method. The method takes
        
//  one parameter, an array of type TInput.
        Type[] parms  =  { TInput.MakeArrayType() };
        factory.SetParameters(parms);

        
//  Set the return type for the method. The return type is
        
//  the generic type parameter TOutput.
        factory.SetReturnType(TOutput);

        
//  Generate a code body for the method. 
        
//  -----------------------------------
        
//  Get a code generator and declare local variables and
        
//  labels. Save the input array to a local variable.
        
//
        ILGenerator ilgen  =  factory.GetILGenerator();

        LocalBuilder retVal 
=  ilgen.DeclareLocal(TOutput);
        LocalBuilder ic 
=  ilgen.DeclareLocal(icollOfTInput);
        LocalBuilder input 
=  ilgen.DeclareLocal(TInput.MakeArrayType());
        LocalBuilder index 
=  ilgen.DeclareLocal( typeof ( int ));

        Label enterLoop 
=  ilgen.DefineLabel();
        Label loopAgain 
=  ilgen.DefineLabel();

        ilgen.Emit(OpCodes.Ldarg_0);
        ilgen.Emit(OpCodes.Stloc_S, input);

        
//  Create an instance of TOutput, using the generic method 
        
//  overload of the Activator.CreateInstance method. 
        
//  Using this overload requires the specified type to have
        
//  a parameterless constructor, which is the reason for adding 
        
//  that constraint to TOutput. Create the constructed generic
        
//  method by passing TOutput to MakeGenericMethod. After
        
//  emitting code to call the method, emit code to store the
        
//  new TOutput in a local variable. 
        
//
        MethodInfo createInst  =
            
typeof (Activator).GetMethod( " CreateInstance " , Type.EmptyTypes);
        MethodInfo createInstOfTOutput 
=
            createInst.MakeGenericMethod(TOutput);

        ilgen.Emit(OpCodes.Call, createInstOfTOutput);
        ilgen.Emit(OpCodes.Stloc_S, retVal);

        
//  Load the reference to the TOutput object, cast it to
        
//  ICollection<TInput>, and save it.
        
//
        ilgen.Emit(OpCodes.Ldloc_S, retVal);
        ilgen.Emit(OpCodes.Box, icollOfTInput);
        ilgen.Emit(OpCodes.Castclass, icollOfTInput);
        ilgen.Emit(OpCodes.Stloc_S, ic);

        
//  Loop through the array, adding each element to the new
        
//  instance of TOutput. Note that in order to get a MethodInfo
        
//  for ICollection<TInput>.Add, it is necessary to first 
        
//  get the Add method for the generic type defintion,
        
//  ICollection<T>.Add. This is because it is not possible
        
//  to call GetMethod on icollOfTInput. The static overload of
        
//  TypeBuilder.GetMethod produces the correct MethodInfo for
        
//  the constructed type.
        
//
        MethodInfo mAddPrep  =  icoll.GetMethod( " Add " );
        MethodInfo mAdd 
=  TypeBuilder.GetMethod(icollOfTInput, mAddPrep);

        
//  Initialize the count and enter the loop.
        ilgen.Emit(OpCodes.Ldc_I4_0);
        ilgen.Emit(OpCodes.Stloc_S, index);
        ilgen.Emit(OpCodes.Br_S, enterLoop);

        
//  Mark the beginning of the loop. Push the ICollection
        
//  reference on the stack, so it will be in position for the
        
//  call to Add. Then push the array and the index on the 
        
//  stack, get the array element, and call Add (represented
        
//  by the MethodInfo mAdd) to add it to the collection.
        
//
        
//  The other ten instructions just increment the index
        
//  and test for the end of the loop. Note the MarkLabel
        
//  method, which sets the point in the code where the 
        
//  loop is entered. (See the earlier Br_S to enterLoop.)
        
//
        ilgen.MarkLabel(loopAgain);

        ilgen.Emit(OpCodes.Ldloc_S, ic);
        ilgen.Emit(OpCodes.Ldloc_S, input);
        ilgen.Emit(OpCodes.Ldloc_S, index);
        ilgen.Emit(OpCodes.Ldelem, TInput);
        ilgen.Emit(OpCodes.Callvirt, mAdd);

        ilgen.Emit(OpCodes.Ldloc_S, index);
        ilgen.Emit(OpCodes.Ldc_I4_1);
        ilgen.Emit(OpCodes.Add);
        ilgen.Emit(OpCodes.Stloc_S, index);

        ilgen.MarkLabel(enterLoop);
        ilgen.Emit(OpCodes.Ldloc_S, index);
        ilgen.Emit(OpCodes.Ldloc_S, input);
        ilgen.Emit(OpCodes.Ldlen);
        ilgen.Emit(OpCodes.Conv_I4);
        ilgen.Emit(OpCodes.Clt);
        ilgen.Emit(OpCodes.Brtrue_S, loopAgain);

        ilgen.Emit(OpCodes.Ldloc_S, retVal);
        ilgen.Emit(OpCodes.Ret);

        
//  Complete the type.
        Type dt  =  demoType.CreateType();
        
//  Save the assembly, so it can be examined with Ildasm.exe.
        demoAssembly.Save(asmName.Name  +   " .dll " );

        
//  To create a constructed generic method that can be
        
//  executed, first call the GetMethod method on the completed 
        
//  type to get the generic method definition. Call MakeGenericType
        
//  on the generic method definition to obtain the constructed
        
//  method, passing in the type arguments. In this case, the
        
//  constructed method has string for TInput and List<string>
        
//  for TOutput. 
        
//
        MethodInfo m  =  dt.GetMethod( " Factory " );
        MethodInfo bound 
=
            m.MakeGenericMethod(
typeof ( string ),  typeof (List < string > ));
        
return  bound;
    }
}

/*  This code example produces the following output:

The first element is: a
System.Collections.Generic.List`1[System.String] Factory[String,List`1](System.String[])
The first element is: a
The first element is: a
 
*/

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值