您的位置:首页 > 编程语言

基于动态代码生成技术的动态对象工厂

2007-03-02 14:48 609 查看
基于动态代码生成技术的动态对象工厂
 
C#中所有的引用类型的实例都需要在运行时动态创建,创建对象实例最常见的办法就是使用new操作符,使用new操作符就需要在编译器明确的知道要创建的对象的类型,如果在编译器并不能明确,就需要用到反射技术,例如:


            String className = "MyNamesapce.MyClass";


            ConstructorInfo ci = Type.GetType(className).GetConstructor(new Type[0]);


            Object o1 = ci.Invoke();


            Object o2 = Activator.CreateInstance(Type.GetType(className);

上述代码展示了两种基于反射的动态对象创建,但这种方法的效率是比较低下的,特别是在需要大量的动态创建实例的时候。为此我们需要一种更为高效的动态创建实例的方法,动态代码生成就是一种不错的方式。
之所以不能直接使用new,就是因为new后面的类型参数在编译器是不知道的,那么就需要在运行的时候动态的创建出与new相配合的代码。这类似于在Javascript中使用eval函数:


var className = “MyClass”;


         var myObj = eval(“new “ + className);



C#并没有像eval这样的函数,毕竟编译型语言和脚本语言是不同的,所以要实现类似的功能,就要使用到System.Reflection.Emit名空间下的类来动态的创建出可执行的代码。首先需要认识几个涉及到的类:
System.Reflection.Emit.AssemblyBuilder:用来动态创建程序集
System.Reflection.Emit.ModuleBuilder:用来动态创建模块
System.Reflection.Emit.TypeBuilder:用来动态创建类型
System.Reflection.Emit.MethodBuilder:用来动态创建方法
这里我的设计思想是,首先创建一个抽象基类(Creator类),它声明了一个用于动态创建需要的对象实例的抽象方法,在运行时根据需要动态的创建出这个抽象类的子类,并动态实现这个抽象方法,编写出用于创建对象的代码。在基类中提供一些静态方法来实现子类的创建过程,并对外提供可调用的方法。这是抽象工厂模式的一种实现。基类的声明如下:


public abstract class Creator




...{


        public abstract Object CreateObject(Object[] param);


        private staticvoid CreateMethod(TypeBuilder tb, Type originalType, Object[] param);


        public static Object New(Type type, params Object[] param) 


}



抽象方法CreateObject就是用来在子类中重写并创建实例的,静态方法CreateMethod用于实现动态代码生成的过程,静态方法New就是对象暴露的方法,使用者通过这个方法来创建需要的实例,从而模拟new操作符,它的两个参数分别代码要创建的变量的类对象、构造函数的参数,这里使用了关键字params来修释,也就是说它成为一个参数个数可变的函数,可以适应各种参数类型的构造函数。
New方法里面首先要动态的创建程序集和模块:

[align=left][/align]


AssemblyBuilder dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("DynamicAssembly"), AssemblyBuilderAccess.Run);


ModuleBuilder moduleBuilder = dynamicAssembly.DefineDynamicModule("MainModule");

参数AssemblyBuilderAccess.Run表示这个动态创建的程序集只用于执行,而不需要保存。有了程序集和模块之后就需要创建Creator类的子类了,也就是工厂类:

[align=left][/align]


TypeBuilder tb = moduleBuilder.DefineType("__dynamicCreator." + type.FullName, TypeAttributes.Public | TypeAttributes.Class, typeof(Creator));


CreateMethod(tb, type, param);


Creator creator = (Creator)Activator.CreateInstance(tb.CreateType());


return creator.CreateObject(param);

这里动态工厂类的类名与要创建的对象的类名相同,名空间前面加上了“__dynamicCreator.”以示区别,参数typeof(Creator)表示这个类要从Creator类继承。然后调用CreateMethod方法来完成动态代码生成,然后调用TypeBuilder的CreateType方法,它会根据之前动态创建的代码生成一个新的类,并在之后可以立即使用,然后我使用Activator.CreateInstance创建出工厂类的实例,之后就可以通过调用这个实例的CreateObject方法来创建出需要的对象了。需要说明的是这里的代码只是一个示例,真正要使用时还需要对创建出的creator对象进行缓存,以后再次创建相同类型的对象时就可以直接使用它的creator对象了。CreateMethod方法是最核心的地方,它需要根据我们指定的类对象和参数找到适当的构造函数,动态为工厂类创建CreateObject方法,在其中调用找到的构造函数,返回构造出的对象。首先得到基类中抽象方法CreateObject的信息:


MethodInfo mi = typeof(Creator).GetMethod("CreateObject");

然后根据这个方法信息创建出子类的同名方法:




MethodBuilder mb = tb.DefineMethod("CreateObject", mi.Attributes & ~MethodAttributes.Abstract, mi.CallingConvention, mi.ReturnType, new Type[] ...{ typeof(Object[]) });

注意这里指定方法属性时需要去除掉基类方法的抽象属性,否则在创建实例时会失败,其他地方都完全和基类方法一样。下面要在被创建对象的类型中查找适当的构造函数。查找的方法是针对每一个构造函数,检查它的参数个数和参数类型与所传入的参数信息是否相容,如果找不到完全相容的构造函数,那么说明用户传入的参数有误,需要抛出异常:


ConstructorInfo[] cis = originalType.GetConstructors(); //反射出所有的构造函数


ConstructorInfo theCi = null;


ParameterInfo[] cpis = null;


foreach(ConstructorInfo ci in cis)




...{


    cpis = ci.GetParameters();


    if (cpis.Length != param.Length) //参数个数不相符


        continue;


    theCi = ci;


    for (int i = 0; i < cpis.Length; i++)




    ...{


        if (!(param[i] == null || param[i].GetType() == cpis[i].ParameterType || param[i].GetType().IsSubclassOf(cpis[i].ParameterType)))     //参数类型不相符




        ...{


theCi = null;


break;


        }


    }


    if (theCi != null) //如果找到了完全相符的构造函数


        break;


}


 


if (theCi == null)


    throw new ArgumentException("错误的参数个数或类型");



现在万事具备,下面就要开始动态生成代码了。要动态的生成可执行代码需要用到ILGenerator类,使用MethodBuilder类的GetILGenerator方法即可以得到这个对象,然后调用它的Emit方法生成中间语言指令:

[align=left][/align]


ILGenerator ilg = mb.GetILGenerator();


for (int i = 0; i < param.Length; i++)




...{这里要循环处理传入的每一个参数,以下通过IL来完成取数组元素并压栈的操作:


    ilg.Emit(OpCodes.Ldarg_1);        //把参数数组放入栈


    ilg.Emit(OpCodes.Ldc_I4, i);      //把下标压入栈


ilg.Emit(OpCodes.Ldelem_Ref);    //以引用的方法从数组中取出需要的内容并放入栈



注意经过Ldelem_Ref指令以后,之前压入栈的两个参数会自动的弹出,而从数组中取得的内容会被放入栈中,所以只要反复的经过上述过程,就可以将传入的参数逐一放入栈中。需要注意的是,这里只处理了引用类型,也就是说如果原来需要的参数是值类弄的,那么参数会在调用函数时被装箱,这里需要还原到原来的值类型,也就是需要一个拆箱操作:

[align=left][/align]


    if (cpis[i].ParameterType.IsValueType)     //判断是否需要拆箱


        ilg.Emit(OpCodes.Unbox_Any, cpis[i].ParameterType); //拆箱为需要的类型


}

Unbox操作也会自动从栈中取出一个元素,拆箱后再把结果放回栈中,也就是说上述过程不会影响栈中元素的个数。经过上述过程,构造函数的参数就已经准备好并放入栈中了,下面就是调用构造函数了:


ilg.Emit(OpCodes.Newobj, theCi);

指令Newobj相当于关键字new,用于调用构函数,参数theCi就是要创建的对象的构造函数信息。经过这个过程原栈中所压的构造函数参数都被弹出,然后把创建后的对象放回到栈中。下面只需要通过Ret指令就可以把栈中唯一的元素作为函数的返回值返回给调用者:


ilg.Emit(OpCodes.Ret); 

到这里所需要的代码就已经动态生成完毕,以后再通过Creator类的子类调用CreateObject方法时,执行的就是上述动态生成的代码了。但只有这些还不够,Creator子类的虚方法表还没有更新,需要调用TypeBuilder类的DefineMethodOverride方法明确的指出用子类中的方法覆盖基类中的虚方法:


tb.DefineMethodOverride(mb, mi);    // 定义方法重载

这段代码虽然比直接使用反射技术要复杂很多,而且里面也多处使用了反射技术,但它只有在第一次使用时被调用到,之后就只调用动态生成的代码,因此对性能影响是可以忽略的。
下面就是要使用上述代码了。假如我们有一个类如下:

[align=left][/align]


public class MyClass




...{




    public MyClass(int p1, string p2) ...{ }


}

而我们在编译时并没有此类的声明,只有保存了类名称的字符串和构造函数的参数类型,那么可以通过如下方法创建实例:


String className = "MyClass";


Type t = Type.GetType(className);


Object o = Creator.New(t, 1, "haha");

用起来还是比较方便的,至少不比反射麻烦。
为了进一步研究这种方法相对于反射方法的优势,我进行了一组实验。首先构造一个类,它有六个构造函数,分别用于测试一个、三个、九个值类型、引用类型参数时的性能:


    public class A




    ...{




        public A(string s, string s2, string s3, string s4, string s5, string s6, string s7, string s8, string s9) ...{ }




        public A(string s, string s2, string s3) ...{ }




        public A(string s) ...{ }




        public A(int a, int b, int c, int d, int e, int f, int g, int h, int i) ...{ }




        public A(int a, int b, int c) ...{ }




        public A(int a) ...{ }


    }

然后通过四种方式调用这六个构造函数来创建实例:Activator.CreateInstance、ConstructorInfo.Invoke、Creator.New、直接使用new,每种调用都重复1000万次,在Intel PentiumM 1.86G、512M内存、Windows XP SP2、.Net Framewor 2.0上测试结果如下:
[align=center]各种调用类型重复1000万次所需时间(毫秒)[/align]
[align=center]调用方式[/align]
[align=center]Activator.[/align]
[align=center]CreateInstance[/align]
[align=center]ConstructorInfo.[/align]
[align=center]Invoke[/align]
[align=center]Creator.New[/align]
[align=center]直接使用new[/align]
[align=center]引用类型[/align]
1个参数
59281.25
18843.75
2296.875
140.625
3个参数
72031.25
24000
2453.125
171.875
9个参数
102843.75
39218.75
3187.5
156.25
[align=center]值类型[/align]
1个参数
60468.75
19921.875
2375
109.375
3个参数
73953.125
26390.625
2796.875
109.375
9个参数
110656.25
46765.625
4453.125
109.375
可见,直接使用new还是最快的,动态代码生成的方法还是要比直接使用new慢了15-40倍,但比使用Activator的方法快20倍左右,比Invoke的方法快10倍左右,因此在不能直接使用new的时候,动态代码生成的方法还是非常实用的。
附完整的源代码如下。此代码仍有一些问题,如当一个类有多个构造函数时,它只能缓存一个构造函数,第二次如果调用另一个则会出错,可以适当的改进来解决此问题。


    public abstract class Creator




    ...{


        private static AssemblyBuilder dynamicAssembly = null;


        private static ModuleBuilder moduleBuilder = null;


        private static Dictionary<Type, Creator> creatorList = new Dictionary<Type, Creator>();


        private static ModuleBuilder GetDynamicModule()




        ...{


            if (dynamicAssembly == null)




            ...{


                dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("DynamicAssembly"), AssemblyBuilderAccess.Run);


                moduleBuilder = dynamicAssembly.DefineDynamicModule("MainModule");


            }


 


            return moduleBuilder;


        }


        


        private static void CreateMethod(TypeBuilder tb, Type originalType, Object[] param)




        ...{


            MethodInfo mi = typeof(Creator).GetMethod("CreateObject");


 




            MethodBuilder mb = tb.DefineMethod("CreateObject", mi.Attributes & ~MethodAttributes.Abstract, mi.CallingConvention, mi.ReturnType, new Type[] ...{ typeof(Object[]) });


 


            ConstructorInfo[] cis = originalType.GetConstructors();


            ConstructorInfo theCi = null;


            ParameterInfo[] cpis = null;


            foreach(ConstructorInfo ci in cis)




            ...{


                cpis = ci.GetParameters();


                if (cpis.Length != param.Length)


                    continue;


 


                theCi = ci;


                for (int i = 0; i < cpis.Length; i++)




                ...{


                    if (!(param[i] == null || param[i].GetType() == cpis[i].ParameterType || param[i].GetType().IsSubclassOf(cpis[i].ParameterType)))




                    ...{


                        theCi = null;


                        break;


                    }


                }


                if (theCi != null)


                    break;


            }


 


            if (theCi == null)


                throw new ArgumentException("错误的参数个数或类型");


 


            ILGenerator ilg = mb.GetILGenerator();


            for (int i = 0; i < param.Length; i++)




            ...{


                ilg.Emit(OpCodes.Ldarg_1);


                ilg.Emit(OpCodes.Ldc_I4, i);


                ilg.Emit(OpCodes.Ldelem_Ref);


                if (cpis[i].ParameterType.IsValueType)


                    ilg.Emit(OpCodes.Unbox_Any, cpis[i].ParameterType);


            }


            ilg.Emit(OpCodes.Newobj, theCi);


            ilg.Emit(OpCodes.Ret);


 


            tb.DefineMethodOverride(mb, mi);    // 定义方法重载


        }


 


        private static Creator GetCreator(Type type, Object[] param)




        ...{


            if(!creatorList.ContainsKey(type))




            ...{


                ModuleBuilder module = GetDynamicModule();


                TypeBuilder tb = module.DefineType("__dynamicCreator." + type.FullName, TypeAttributes.Public | TypeAttributes.Class, typeof(Creator));


                CreateMethod(tb, type, param);


                creatorList.Add(type, (Creator)Activator.CreateInstance(tb.CreateType()));


            }


            return creatorList[type];


        }


 


        public abstract Object CreateObject(Object[] param);


 


        public static Object New(Type type, params Object[] param)




        ...{


            Creator creator = GetCreator(type, param);


            return creator.CreateObject(param);


        }


 }

  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息