现在的位置: 首页 > 综合 > 正文

Expression Tree 创建任意多个参数的构造函数Lambda表达式

2013年04月12日 ⁄ 综合 ⁄ 共 4275字 ⁄ 字号 评论关闭

上一篇文章我介绍了使用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秒左右。所以,如果构建合适的缓存可以提高不少性能)

总结

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

抱歉!评论已关闭.