您的位置:首页 > 其它

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

2011-09-18 10:40 471 查看

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

总结

如何解决对委托进行缓存的问题呢?虽然这已不是本文的范围,但希望能有人继续研究,并实现最优方案。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐