表达式树对性能的影响
至于表达式树是啥我就不想说了,大家可以参考脑袋的相关文章。一至以来,我虽然接触过表达式树,还写过一篇表达式树和泛型委托相关的文章,但终究只现在表面功能,并没有真正认识到它的好处。这两天我看了脑袋的相关文章,每篇都给我们留下了一些练习题,有一道是关于一次性修改实体集中相关属性的问题。
原题:日常应用中我们常常会和一些数据实体类打交道。现在假设我们的数据实体类里面定义了许多public的属性。请用Expression Tree实现一个动态逻辑,可以更新任意一种数据实体类的集合,比如List<Book>, List<Employee>等等中每一个元素的指定属性。比如我要对一个集合中所有Book元素的Author属性进行更新,您的程序就可以这样调用:
List<Book> books = …
SetAllProperty(books, “Author”, “Ninputer”);
当然您写的方法不知道Book类型,也不知道我要更新哪个属性,它应该能动态地支持任意的实体类集合。
首先我想到的就是遍历实体的属性,找对就修改的属性,然后调用SetValue方式就OK,代码也不多,如下:
{
if (null == t||null ==propertyName ||null ==propertyValue )
{
return null ;
}
for (int i = 0; i < t.Count; i++)
{
PropertyInfo[] proAs = t[i].GetType().GetProperties();
foreach (PropertyInfo proA in proAs)
{
if (!proA.CanRead &&!proA .CanWrite ) continue;
if (proA.Name.ToLower()==propertyName .ToString ().ToLower ())
{
proA.SetValue(t[i], propertyValue , null);
}
}
}
return t;
}
问题:上面的代码理论上和实际操作上都能够很好的完成练习题,但有个性能问题,如果这个实体集特别大的时候,比如一百万或者更多,此时就会特别耗时。于时我在脑袋的指点下,花了大半天时间终于有所结果:
版本一:结果都是正确的,问题时,这种写法比遍历属性赋值方式更慢。问题就在于LambdaExpression lambda = Expression.Lambda(call, parameter, value);var exp = lambda.Compile();这两条语句上,这个表达式经过Compile()后会生成一个Delegate,Compile方法在内部使用了Emit,而问题就出了DynamicInvoke上面,DynamicInvoke方法其本质与反射调用差不多。
{
ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
ParameterExpression value = Expression.Parameter(typeof(string ), "propertyValue");
MethodInfo setter = typeof(T).GetMethod("set_"+propertyName );
MethodCallExpression call = Expression.Call(parameter, setter, value);
LambdaExpression lambda = Expression.Lambda(call, parameter, value);
var exp = lambda.Compile();
for (int i = 0; i < collection.Count; i++)
{
exp.DynamicInvoke(collection[i], propertyValue);
}
return collection;
}
版本二:生成强类型委托。强类型委托改掉了DynamicInvoke带来的性能问题。
说明:强类型委托我知道的有两种方式:
第一:Func<(T, TResult> 泛型委托,当然这种委托还包含一个参数以上的方式,但它需要有返回值。
第二:Action<T, 参数类型>,与Func不同的就是它并不需要返回值。所以下面我采用了Action方式。
{
ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
ParameterExpression value = Expression.Parameter(typeof(string), "propertyValue");
MethodInfo setter = typeof(T).GetMethod("set_" + propertyName);
MethodCallExpression call = Expression.Call(parameter, setter, value);
var lambda = Expression.Lambda<Action<T, string>>(call, parameter, value);
var exp = lambda.Compile();
for (int i = 0; i < collection.Count; i++)
{
exp(collection[i], propertyValue);
}
return collection;
}
版本二和反射方式的性能比较:先帖下相关代码
1:实体类:
{
public string sTitle
{ get; set; }
public string sContent
{ get; set; }
public int iID
{ get; set; }
}
2:修改任务泛型实体集属性类:上面都有,就不重复了。
3:构造一个包含一百万的实体集:
{
List<Book> list = new List<Book>();
for (int i = 0; i < 1000000; i++)
{
Book b = new Book();
b.sTitle = "标题" + i.ToString();
b.sContent = "内容" + i.ToString();
b.iID = i;
list.Add(b);
}
return list;
}
4:性能比较:
SetAllPropertyMethod<Book> s = new SetAllPropertyMethod<Book>();
Stopwatch sw = new Stopwatch();
sw.Start();
list = s.SetAllProperty(list, "sTitle", "1");
sw.Stop();
double time1 = sw.ElapsedMilliseconds;
Response.Write("反射方式用时:"+time1 .ToString ());
sw = new Stopwatch();
sw.Start();
list = s.ExpressionTree<Book>(list, "sTitle", "1");
sw.Stop();
time1 = sw.ElapsedMilliseconds;
Response.Write("表达式树用时:" + time1.ToString());
5:输出结果如下:反射方式用时:3198表达式树用时:38 ,可能看出接近100倍的性能优势。
说话这个Stopwatch原来我也没有用过,汗,编码快四年了,居然这种好东西没用过。是不是有些朋友也和我一要只会利用TimeSpan来比较呢,哈哈。
说明:这种表达式树只生成一次,即可完成整个实体集的应用,起到了非常好的复用作用。
表达式树的优势:
表达式树拥有语义清晰,强类型等优势,表达式树的计算对于性能的影响会越来越大,由于减少了编译操作和反射操作的次数,计算所需开销大大降低。