Activator.CreateInstance 方法创建对象和Expression Tree创建对象性能的比较(构造函数含多参数的情况)
Expression Tree 创建任意多个参数的构造函数Lambda表达式
上一篇文章我介绍了使用Expression Tree 来创建带参数的构造函数Lambda表达式,但不是任意多个参数。当天晚上看到Ivony的留言,顿时有了一点灵感,决定再深入一下。
固定参数:
上一篇文章只是针对固定参数(例如1个或两个参数的情况)来构建表达式,实际上构建表达式是:
Expression<Func<int, string, object>> createInstanceExp = (arg1, arg2) => new Bar(arg1, arg2);
使用这个表达式生成的委托并缓存它,性能是很优的。但对于任意多个参数的情况,迫使我把以上表达式丢掉,至少暂时置于一边,而寻求以下表达式——
任意多个参数:
Expression<Func<object[], object>> createInstanceExp = (args) => new Bar((int)args[0], (string)args[1], ...);
相对于前者,多了一层访问数组以及显式转换,效率明显会低一点,不过应用更广。
下面主要说明怎样建立这个表达式
/// <summary> /// 创建用来返回构造函数的委托 /// </summary> /// <param name="type">类型</param> /// <param name="parameterTypes">构造函数的参数类型数组</param> /// <returns></returns> public static Func<object[], object> CreateInstanceDelegate(this Type type, Type[] parameterTypes) { //根据参数类型数组来获取构造函数 var constructor = type.GetConstructor(parameterTypes); //创建lambda表达式的参数 var lambdaParam = Expression.Parameter(typeof(object[]), "_args"); //创建构造函数的参数表达式数组 var constructorParam = buildParameters(parameterTypes, lambdaParam); //创建构造函数表达式 NewExpression newExp = Expression.New(constructor, constructorParam); //创建lambda表达式,返回构造函数 Expression<Func<object[], object>> lambdaExp = Expression.Lambda<Func<object[], object>>(newExp, lambdaParam); return lambdaExp.Compile(); } /// <summary> /// 根据类型数组和lambda表达式的参数,转化参数成相应类型 /// </summary> /// <param name="parameterTypes">类型数组</param> /// <param name="paramExp">lambda表达式的参数表达式(参数是:object[])</param> /// <returns>构造函数的参数表达式数组</returns> static Expression[] buildParameters(Type[] parameterTypes, ParameterExpression paramExp) { List<Expression> list = new List<Expression>(); for (int i = 0; i < parameterTypes.Length; i++) { //从参数表达式(参数是:object[])中取出参数 var arg = BinaryExpression.ArrayIndex(paramExp, Expression.Constant(i)); //把参数转化成指定类型 var argCast = Expression.Convert(arg, parameterTypes[i]); list.Add(argCast); } return list.ToArray(); }
其实上一篇没有做到的就是创建构造函数的参数表达式这一部分。
BinaryExpression.ArrayIndex(paramExp, Expression.Constant(i)) 是实现取数据元素 args[i] ;
Expression.Convert(arg, parameterTypes[i]) 是实现对 args[i] 进行显式转换;
怎么使用?
Type type = typeof(Bar); var createInstance1 = type.CreateInstanceDelegate(new Type[] { typeof(int) }); object obj1 = createInstance1(new object[] { 123 }); var createInstance2 = type.CreateInstanceDelegate(new Type[] { typeof(int), typeof(string) }); object obj2 = createInstance2(new object[] { 123, "Bruce" });
Oh,No!难看到晕了...
CreateInstanceDelegate 估计只是个裸奔的方法,一般不会直接使用。我给它做件衣服披一下。
/// <summary> /// 创建实例 /// </summary> /// <param name="type">类型</param> /// <param name="args">构造函数的参数列表</param> /// <returns></returns> public static object CreateInstance(this Type type, params object[] args) { Func<object[], object> createInstanceDelegate; //根据参数列表返回参数类型数组 var parameterTypes = args.Select(c => c.GetType()).ToArray(); //从缓存中获取构造函数的委托(注:key是 type 和 parameterTypes) if (!cache.TryGetValue(type, parameterTypes, out createInstanceDelegate)) { //缓存中没有找到,新建一个构造函数的委托 createInstanceDelegate = type.CreateInstanceDelegate(parameterTypes); //缓存构造函数的委托 cache.Add(type, parameterTypes, createInstanceDelegate); } return createInstanceDelegate(args); }
(cache是为以上方法专门写的缓存类,但测试结果反映似乎它成了性能的瓶颈,但还好总体速度比Activator.CreateInstance 快,否则就没什么价值可言了)
现在可以直接使用了:
Type type = typeof(Bar); object obj1 = type.CreateInstance(123); object obj2 = type.CreateInstance(123, "Bruce");
测试代码:
const int count = 500000; static void Test4() { Console.WriteLine("Test4 - CreateInstance(带n参数): "); Stopwatch watcher = new Stopwatch(); Type type = typeof(Bar); watcher.Reset(); watcher.Start(); for (int i = 0; i < count; i++) { Activator.CreateInstance(type, i); Activator.CreateInstance(type, i, "string"); } watcher.Stop(); Console.WriteLine("Activator: " + watcher.Elapsed); watcher.Reset(); watcher.Start(); for (int i = 0; i < count; i++) { type.CreateInstance(i); type.CreateInstance(i, "string"); } watcher.Stop(); Console.WriteLine("Expression(任意): " + watcher.Elapsed); watcher.Reset(); watcher.Start(); var createInstance1 = type.CreateInstanceDelegate<int>(); var createInstance2 = type.CreateInstanceDelegate<int, string>(); for (int i = 0; i < count; i++) { createInstance1(i); createInstance2(i, "string"); } watcher.Stop(); Console.WriteLine("Expression(固定): " + watcher.Elapsed); watcher.Reset(); watcher.Start(); for (int i = 0; i < count; i++) { new Bar(i); new Bar(i, "string"); } watcher.Stop(); Console.WriteLine("Direct: " + watcher.Elapsed); Console.WriteLine(); }
测试结果:
Test4 - CreateInstance(带n参数): Activator: 00:00:02.9804000 Expression(任意): 00:00:01.0157791 Expression(固定): 00:00:00.0178235 Direct: 00:00:00.0045335
(补充一个:如果直接用文中说到的那个裸奔方法,则测试时间是0.03秒左右。所以,如果构建合适的缓存可以提高不少性能)
总结
如何解决对委托进行缓存的问题呢?虽然这已不是本文的范围,但希望能有人继续研究,并实现最优方案。