[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