[C#.NET] 利用 Expression Tree 提昇反射效率-動態屬性
續上篇,[C#.NET] 利用 Expression Tree 提昇反射效率 Expression 能夠寫出高效率的反射
這篇將處理類別屬性
使用 Reflection 類別處理屬性,核心片斷程式碼如下
{ if (instance == null) { return; } var propertyInfo = instance.GetType().GetProperty(memberName); if (propertyInfo == null) { return; } propertyInfo.SetValue(instance, newValue, null); } private static object Get(object instance, string memberName) { if (instance == null) { return null; } var propertyInfo = instance.GetType().GetProperty(memberName); if (propertyInfo == null) { return null; } return propertyInfo.GetValue(instance, null); }
完整程式碼如下:
使用 Expression 類別處理屬性
- 因為 Expression 與 IL Emit 本質是相同的東西,所以就不加入 IL Emit 測試,參考:关于Expression Tree和IL Emit的所谓的"性能差别"
- Expression 處理反射能具有較高的效率,但也不易編寫,跟 IL Emit 比起來,Expression 好寫多了
- 核心片斷程式碼參考來源,參考:不使用反射进行C#属性的运行时动态访问
- 核心片斷程式碼如下:
{ var type = typeof(T); var instance = Expression.Parameter(typeof(object), "instance"); var memberName = Expression.Parameter(typeof(string), "memberName"); var nameHash = Expression.Variable(typeof(int), "nameHash"); var getHashCode = Expression.Assign(nameHash, Expression.Call(memberName, typeof(object).GetMethod("GetHashCode"))); var cases = new List<SwitchCase>(); foreach (var propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { var property = Expression.Property(Expression.Convert(instance, typeof(T)), propertyInfo.Name); var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof(int)); cases.Add(Expression.SwitchCase(Expression.Convert(property, typeof(object)), propertyHash)); } var switchEx = Expression.Switch(nameHash, Expression.Constant(null), cases.ToArray()); var methodBody = Expression.Block(typeof(object), new[] { nameHash }, getHashCode, switchEx); return Expression.Lambda<Func<object, string, object>>(methodBody, instance, memberName).Compile(); } private static Action<object, string, object> GenerateSetValue() { var type = typeof(T); var instance = Expression.Parameter(typeof(object), "instance"); var memberName = Expression.Parameter(typeof(string), "memberName"); var newValue = Expression.Parameter(typeof(object), "newValue"); var nameHash = Expression.Variable(typeof(int), "nameHash"); var getHashCode = Expression.Assign(nameHash, Expression.Call(memberName, typeof(object).GetMethod("GetHashCode"))); var cases = new List<SwitchCase>(); foreach (var propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { var property = Expression.Property(Expression.Convert(instance, typeof(T)), propertyInfo.Name); var setValue = Expression.Assign(property, Expression.Convert(newValue, propertyInfo.PropertyType)); var propertyHash = Expression.Constant(propertyInfo.Name.GetHashCode(), typeof(int)); cases.Add(Expression.SwitchCase(Expression.Convert(setValue, typeof(object)), propertyHash)); } var switchEx = Expression.Switch(nameHash, Expression.Constant(null), cases.ToArray()); var methodBody = Expression.Block(typeof(object), new[] { nameHash }, getHashCode, switchEx); return Expression.Lambda<Action<object, string, object>>(methodBody, instance, memberName, newValue).Compile(); }
完整程式碼如下:
接著我將要測試它們運行的效果
- TestInfo 是測試結果的輸出,完整程式碼如下:
https://dotblogsamples.codeplex.com/SourceControl/latest#Simple.Expressions/UnitTestProject1/TestInfo.cs - 測試項目:
-
- 測直接處理屬性
- Expression反射
- Reflection反射
- 測試次數:776286
- 片斷程式碼如下
public void GetAndSetValue_Test() { var runTimes = 776286; var expected = new FactProductInventory(); var dynamicProperty = new DynamicProperty<FactProductInventory>(); var reflectionProperty = new ReflectionProperty<FactProductInventory>(); var test1 = new TestInfo(() => { var inventory = new FactProductInventory(); inventory.DateKey = expected.DateKey; inventory.MovementDate = expected.MovementDate; inventory.ProductKey = expected.ProductKey; inventory.UnitCost = expected.UnitCost; inventory.UnitsBalance = expected.UnitsBalance; inventory.UnitsIn = expected.UnitsIn; inventory.UnitsOut = expected.UnitsOut; }, "直接指定,GetAndSetValue"); test1.Run(runTimes); var test2 = new TestInfo(() => { var inventory = new FactProductInventory(); foreach (var propertyName in s_propertyNames) { var field = propertyName.Key; var value = dynamicProperty.GetValue(expected, field); dynamicProperty.SetValue(inventory, field, value); } }, "Expression反射,GetAndSetValue"); test2.Run(runTimes); var test3 = new TestInfo(() => { var inventory = new FactProductInventory(); foreach (var propertyName in s_propertyNames) { var field = propertyName.Key; var value = reflectionProperty.GetValue(expected, field); reflectionProperty.SetValue(inventory, field, value); } }, "Reflection反射,GetAndSetValue"); test3.Run(runTimes); Assert.AreEqual(test1.RunCount, runTimes); Assert.AreEqual(test2.RunCount, runTimes); Assert.AreEqual(test3.RunCount, runTimes); }
執行結果如下圖:
結論:
直接處理屬性是最快的,若需要動態的處理屬性,這時使用 Expression Tree,可以得到較好的性能
參考來源:
http://www.cnblogs.com/nankezhishi/archive/2012/02/11/dynamicaccess.html
http://www.cnblogs.com/artech/archive/2011/03/27/ExpressTreeVsIlEmit.html
http://www.cnblogs.com/artech/archive/2011/03/24/PropertyAccessor.html
http://blog.163.com/dreamman_yx/blog/static/26526894201092825312949/
本文出自:http://www.dotblogs.com.tw/yc421206/archive/2015/03/16/150737.aspx
專案連結:https://dotblogsamples.codeplex.com/SourceControl/latest#Simple.Expressions/
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET