Reflection與ExpressionTree的效能比較
今天看到黑大發的這篇 Reflection執行效能測試 文章
剛好最近有在玩ExpressionTree,所以也補上用ExpressionTree做對屬性賦值的效能比較
先貼上測試Code及測試結果
class Program
{
static void Main(string[] args)
{
//因為沒有資料庫的資料來源,所以用集合模擬資料
List<Dictionary<string, string>> listDictionary = InitializeTestData();
Console.WriteLine("Row Count: {0:N0}", listDictionary.Count);
Stopwatch sw = new Stopwatch();
List<TestClass> list = new List<TestClass>();
Console.WriteLine("==== HardCode Start====");
sw.Reset();
sw.Start();
foreach (var row in listDictionary)
{
TestClass c = new TestClass();
c.C1 = row["C1"];
c.C2 = row["C2"];
c.C3 = row["C3"];
c.C4 = row["C4"];
c.C5 = row["C5"];
c.C6 = row["C6"];
c.C7 = row["C7"];
c.C8 = row["C8"];
c.C9 = row["C9"];
c.C10 = row["C10"];
list.Add(c);
}
sw.Stop();
Console.WriteLine("==== HardCode End====: {0:N0}", sw.ElapsedMilliseconds);
//===================================================================
list = new List<TestClass>();
Console.WriteLine("==== Reflection Cache Version Start====");
sw.Reset();
sw.Start();
foreach (var row in listDictionary)
{
TestClass c = new TestClass();
foreach (var key in row.Keys)
{
ReflectionFactory.Set(c, row[key], key);
}
list.Add(c);
}
sw.Stop();
Console.WriteLine("==== Reflection Cache Version End====: {0:N0}", sw.ElapsedMilliseconds);
//=====================================================================
list = new List<TestClass>();
Console.WriteLine("==== ExpressionTree Start====");
sw.Reset();
sw.Start();
foreach (var row in listDictionary)
{
TestClass c = new TestClass();
foreach (var key in row.Keys)
{
ExpressionFactory.Set(c, row[key], key);
}
list.Add(c);
}
sw.Stop();
Console.WriteLine("==== ExpressionTree End====: {0:N0}", sw.ElapsedMilliseconds);
Console.ReadKey();
}
static List<Dictionary<string, string>> InitializeTestData()
{
List<Dictionary<string, string>> data = new List<Dictionary<string, string>>();
for (int i = 0; i < 100000; i++)
{
Dictionary<string, string> dic = new Dictionary<string, string>();
dic["C1"] = Guid.NewGuid().ToString();
dic["C2"] = Guid.NewGuid().ToString();
dic["C3"] = Guid.NewGuid().ToString();
dic["C4"] = Guid.NewGuid().ToString();
dic["C5"] = Guid.NewGuid().ToString();
dic["C6"] = Guid.NewGuid().ToString();
dic["C7"] = Guid.NewGuid().ToString();
dic["C8"] = Guid.NewGuid().ToString();
dic["C9"] = Guid.NewGuid().ToString();
dic["C10"] = Guid.NewGuid().ToString();
data.Add(dic);
}
return data;
}
}
public class TestClass
{
public string C1 { get; set; }
public string C2 { get; set; }
public string C3 { get; set; }
public string C4 { get; set; }
public string C5 { get; set; }
public string C6 { get; set; }
public string C7 { get; set; }
public string C8 { get; set; }
public string C9 { get; set; }
public string C10 { get; set; }
}
測試結果
雖然跟直接對屬性塞值的效能還是有一段差距,但比起Reflection已經好一些了。
p.s 在這個測試中我有對 PropertyInfo 做 Cache
如果對這個結果還可以接受的話,接下來就來看看實作的方式吧
Reflection
public class ReflectionFactory
{
//用來對 PropertyInfo 做 Cache
public static Dictionary<PropertyKey, PropertyInfo> PropertyCache = new Dictionary<PropertyKey, PropertyInfo>();
public static object lockObject = new object();
public static void Set(object obj, object value, string propertyName)
{
PropertyKey key = new PropertyKey(obj.GetType(), propertyName);
//檢查是否已存在此PropertyInfo
if (!PropertyCache.ContainsKey(key))
{
lock (lockObject)
{
if (!PropertyCache.ContainsKey(key))
{
//取得PropertyInfo
var propertyInfo = obj.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
//塞值
propertyInfo.SetValue(obj, value, null);
//存入Cache,下次就不用再做GetProperty的動作了
PropertyCache[key] = propertyInfo;
return;
}
}
}
PropertyCache[key].SetValue(obj, value, null);
}
}
ExpressionTree
public class ExpressionFactory
{
public static Dictionary<PropertyKey, Action<object, object>> PropertyCache = new Dictionary<PropertyKey, Action<object, object>>();
public static object lockObject = new object();
public static void Set(object obj,object value, string propertyName)
{
PropertyKey key = new PropertyKey(obj.GetType(), propertyName);
//先檢查有沒有該屬性的委派方法(塞值)
if (!PropertyCache.ContainsKey(key))
{
lock (lockObject)
{
if (!PropertyCache.ContainsKey(key))
{
//沒有的話,就建立利用ExpressionTree建立委派
var action = CreateSetAction(obj, propertyName);
//放入Cache
PropertyCache[key] = action;
//執行賦值動作
action(obj, value);
return;
}
}
}
PropertyCache[key](obj,value);
}
private static Action<object, object> CreateSetAction(object obj, string propertyName)
{
var propertyInfo = obj.GetType().GetProperty(propertyName);
//參數1 目標物件
var targetObj = Expression.Parameter(typeof(object), "obj");
//參數2 值
var propertyValue = Expression.Parameter(typeof(object), "value");
//主體
//第一個參數:由於上方參數1是object型別,因此先轉成目標物件型別
//第二個參數:Set Property 的方法主體
//第三個參數:上方的參數2是object型別,因此要轉成目標屬性型別
var setMethod = Expression.Call(
Expression.Convert(targetObj, obj.GetType()),
propertyInfo.GetSetMethod(),
Expression.Convert(propertyValue,
propertyInfo.PropertyType)
);
//將此 ExpressionTree 編譯成 Lambda,沒有回傳值,兩個參數的委派方法
return Expression.Lambda<Action<object, object>>(setMethod, targetObj, propertyValue).Compile();
}
}
另外Cache的實作方式是把我要的東西放在一個靜態集合裡面
key用Type及名稱建立一個PropertyKey物件,並override GetGashCode及Equals
public class PropertyKey
{
public Type PropertyType { get; private set; }
public string PropertyName { get; private set; }
public PropertyKey(Type type, string propertyName)
{
PropertyType = type;
PropertyName = propertyName;
}
public override int GetHashCode()
{
return PropertyType.GetHashCode() ^ PropertyName.GetHashCode();
}
public override bool Equals(object obj)
{
return this.GetHashCode().Equals(obj.GetHashCode());
}
}
這樣對同一個Type的屬性做操作時,可以不用重複做一些耗效能的動作
(當然這是在大量重複利用的情況下才看得出效果)
這邊Expression的用法是針對某個Type的某個屬性,產生一個委派方法
例如 TestClass的C1屬性,在跑過CreateSetAction之後,會產生一個好比
Action<object, object> action = SetC1;
public void SetC1(object obj,object value)
{
((TestClass)obj).C1 = (string)value;
}
這樣的東西出來(以上只是示意,實際上不會完全一樣,但就是一個匿名的委派)
因此可以直接用 action(實體,值) 這樣去塞值進去,並且將這個匿名委派Cache起來,
以便可以重複使用。
大概是這個樣子,上面的Code是我打算拿來當作自己的Library用的,目前是第一版
可能有些地方還沒有考慮到或不夠周詳,再慢慢改進中...