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.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 如果带多个参数,速度明显下降,而后两者则影响不大。

posted on 2013-08-04 19:26  王丹小筑  阅读(497)  评论(0)    收藏  举报

导航