您的位置:首页 > 其它

Activator.CreateInstance 方法创建对象和Expression Tree创建对象性能的比较(构造函数含多参数的情况)

2009-12-09 20:07 573 查看

前言

标题实在是太长了,是吗?其实并无什么高深的内容。今天看了 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.CreateInstanceExpression 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 如果带多个参数,速度明显下降,而后两者则影响不大。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐