Activator.CreateInstance 方法创建对象和Expression Tree创建对象性能的比较(构造函数含多参数的情况)
转自 http://www.cnblogs.com/coolcode/archive/2009/12/09/ExpressionCreateInstance.html
前言
标题实在是太长了,是吗?其实并无什么高深的内容。今天看了 Will Meng 的 《Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较(终结版)》一文,请没有看过该博文的朋友先前往围观,或许会得到一些启发。而我想知道如果创建对象带参数会是什么样的情况呢,可惜没有看到,于是自己动手试试,作为前文的一点补充。而本文另一个目的是,对于多参数的情况,本人提供的实现方法实在是不雅,请高手指点如何重构它们成为一个通用的版本。
Expression Tree 创建对象
1. 无参数的情况
public static Func<object> CreateInstanceDelegate(this Type type) { NewExpression newExp = Expression.New(type); Expression<Func<object>> lambdaExp = Expression.Lambda<Func<object>>(newExp, null); Func<object> func = lambdaExp.Compile(); return func; }
(注:以上方法是扩展方法,请放到一个static class里)
测试:
Type type = typeof(Bar); var createInstance = type.CreateInstanceDelegate(); for (int i = 0; i < 1000000; i++) { createInstance(); }
好的,如果你看明白了上面的代码,那么接着看下去才有意义。
2. 多参数情况
我试图一步到位创建以下通用的含多参数的创建对象方法:
Func<object[], object> CreateInstanceDelegate(Type type, params object[] args)
结果多次修改尝试仍然失败,虽然我知道失败的原因,但不知道如何修正它。
//以下代码有bug! public static Func<object[], object> CreateInstanceDelegate(this Type type, params object[] args) { var construtor = type.GetConstructor(args.Select(c => c.GetType()).ToArray()); var param = buildParameters(args); NewExpression newExp = Expression.New(construtor, param); Expression<Func<object[], object>> lambdaExp = Expression.Lambda<Func<object[], object>>(newExp, param); Func<object[], object> func = lambdaExp.Compile(); return func; } static ParameterExpression[] buildParameters(object[] args) { int i = 0; List<ParameterExpression> list = new List<ParameterExpression>(); foreach (object arg in args) { list.Add(Expression.Parameter(arg.GetType(), "arg" + (i++))); } return list.ToArray(); }
(注:再次说明以上代码有bug,不能使用)
以上方法中的Lambda表达式“Expression<Func<object[], object>> ”已经定义参数是object[], 而构造函数的参数却不能自动转化。当使用以下代码作测试,
var createInstance = typeof(Bar).CreateInstanceDelegate(0, "");
结果报“Incorrect number of parameters supplied for lambda declaration” 错误。或许需要作一个表达式Convertor 就可以,不过暂时没进一步探究。
无奈之下,我唯有先搞定一个参数的情况,
public static Func<T, object> CreateInstanceDelegate<T>(this Type type) { Type paramType = typeof(T); var construtor = type.GetConstructor(new Type[] { paramType }); var param = new ParameterExpression[] { Expression.Parameter(paramType, "arg") }; NewExpression newExp = Expression.New(construtor, param); Expression<Func<T, object>> lambdaExp = Expression.Lambda<Func<T, object>>(newExp, param); Func<T, object> func = lambdaExp.Compile(); return func; }
通过使用泛型,创建Lambda表达式时不会报参数不匹配的错误了。
这里定义一个Bar类型作测试用,有3个构造函数:
public class Bar { public Bar() { } public Bar(int num) { } public Bar(int num, string str) { } }
测试例子:
Type type = typeof(Bar); var createInstance = type.CreateInstanceDelegate<int>(); for (int i = 0; i < count; i++) { createInstance(i); }
这里列出 Activator.CreateInstance,Expression Tree 和直接使用 new 的性能测试代码:
static void Test2() { Console.WriteLine("Test2 - CreateInstance(带1参数): "); Stopwatch watcher = new Stopwatch(); Type type = typeof(Bar); watcher.Reset(); watcher.Start(); for (int i = 0; i < count; i++) { Activator.CreateInstance(type, i); } watcher.Stop(); Console.WriteLine("Activator: " + watcher.Elapsed); watcher.Reset(); watcher.Start(); var createInstance = type.CreateInstanceDelegate<int>(); for (int i = 0; i < count; i++) { createInstance(i); } watcher.Stop(); Console.WriteLine("Expression: " + watcher.Elapsed); watcher.Reset(); watcher.Start(); for (int i = 0; i < count; i++) { new Bar(i); } watcher.Stop(); Console.WriteLine("Direct: " + watcher.Elapsed); Console.WriteLine(); }
测试通过,而且性能也优于Activator.CreateInstance,详细结果请看文章末的总体测试结果。
问题总结:
实际开发中,我希望得到的版本是跟Activator.CreateInstance 一样的:
public static object CreateInstance(Type type, params object[] args);
那么,得到问题一:如何来封装以下方法
Func<T, object> CreateInstanceDelegate<T>(this Type type)
如何缓存这种委托 Func<T, object> ?
使用Expression Tree创建含两个参数的构造函数,虽然也是照葫芦画瓢的事情,如
public static Func<T1, T2, object> CreateInstanceDelegate<T1, T2>(this Type type) { var types = new Type[] { typeof(T1), typeof(T2) }; var construtor = type.GetConstructor(types); int i = 0; var param = types.Select(t => Expression.Parameter(t, "arg" + (i++))).ToArray(); NewExpression newExp = Expression.New(construtor, param); Expression<Func<T1, T2, object>> lambdaExp = Expression.Lambda<Func<T1, T2, object>>(newExp, param); Func<T1, T2, object> func = lambdaExp.Compile(); return func; }
但是这是个好方法吗?我想不是,那么得到问题二: 对于任意多个参数应该怎么写才合理?
代码下载:CoolCode.Linq.Expressions.rar
总体测试结果:
测试次数皆是一百万次
Test1 - CreateInstance(无参数): Activator: 00:00:00.1673892 Expression: 00:00:00.0126696 Direct: 00:00:00.0067769 Test2 - CreateInstance(带1参数): Activator: 00:00:02.9516177 Expression: 00:00:00.0135498 Direct: 00:00:00.0074452 Test3 - CreateInstance(带2参数): Activator: 00:00:03.1932820 Expression: 00:00:00.0151513 Direct: 00:00:00.0063228
以上结果显示 Activator.CreateInstance 如果带多个参数,速度明显下降,而后两者则影响不大。
This posting is provided "AS IS" with no warranties, and confers no rights.
浙公网安备 33010602011771号