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], ...);

(省略号表示表达式将会是根据args这个参数来动态生成的)

相对于前者,多了一层访问数组以及显式转换,效率明显会低一点,不过应用更广。

下面主要说明怎样建立这个表达式

/// <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秒左右。所以,如果构建合适的缓存可以提高不少性能)

总结

如何解决对委托进行缓存的问题呢?虽然这已不是本文的范围,但希望能有人继续研究,并实现最优方案。

你可能感兴趣的:(createInstance)