記錄日誌時, 常常須要描述對象的狀態發生了怎樣的變化, 之前處理的很是簡單粗暴:緩存
a. 重寫class的ToString()方法, 將重要的屬性都輸出來app
b. 記錄日誌時: 誰誰誰 由 變動前實例.ToString() 變成 變動後實例.ToString()ide
但輸出的日誌老是太長了, 翻看日誌時想找到差別也很是麻煩, 因此想輸出爲: 誰誰誰的哪一個屬性由 aaa 變成了 bbb性能
手寫代碼一個一個的比較字段而後輸出這樣的日誌信息, 是不敢想象的事情. 原本想參考Dapper使用 System.Reflection.Emit 發射 來提升運行效率, 但實在沒有功夫研究.Net Framework的中間語言, 因此準備用 Attribute特性 和 反射 來實現測試
/// <summary> /// 要比較的字段或屬性, 目前只支持C#基本類型, 好比 int, bool, string等, 你本身寫的class或者struct 須要重寫 ToString()、Equals(), 按理說若是重寫了Equals(), 那也須要重寫GetHashCode(), 但確實沒有用到GetHashCode(), 因此能夠忽略Warning不重寫GetHashCode(); /// </summary> [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public class ComparePropertyFieldAttribute : Attribute { /// <summary> /// 屬性或字段的別名 /// </summary> public string PropertyName { get; private set; } /// <summary> /// 要比較的字段或屬性 /// </summary> public ComparePropertyFieldAttribute() { } /// <summary> /// 要比較的字段或屬性 /// </summary> /// <param name="propertyName">屬性或字段的別名</param> public ComparePropertyFieldAttribute(string propertyName) { PropertyName = propertyName; } // 緩存反射的結果, Tuple<object, ComparePropertyAttribute> 中第一個參數之因此用object 是由於要保存 PropertyInfo 和 FieldInfo private static Dictionary<Type, Tuple<object, ComparePropertyFieldAttribute>[]> dict = new Dictionary<Type, Tuple<object, ComparePropertyFieldAttribute>[]>(); /// <summary> /// 只對帶有ComparePropertyAttribute的屬性和字段進行比較 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="from"></param> /// <param name="to"></param> /// <param name="differenceMsg">不相同的字段或屬性 的字符串說明</param> /// <returns>二者相同時, true; 二者不相同時, false</returns> public static bool CompareDifference<T>(T from, T to, out string differenceMsg) { var type = typeof(T); lock (dict) { if (!dict.ContainsKey(type)) { var list = new List<Tuple<object, ComparePropertyFieldAttribute>>(); // 獲取帶ComparePropertyAttribute的屬性 var properties = type.GetProperties(); foreach (var property in properties) { var comparePropertyAttribute = (ComparePropertyFieldAttribute)property.GetCustomAttributes(typeof(ComparePropertyFieldAttribute), false).FirstOrDefault(); if (comparePropertyAttribute != null) { list.Add(Tuple.Create<object, ComparePropertyFieldAttribute>(property, comparePropertyAttribute)); } } // 獲取帶ComparePropertyAttribute字段 var fields = type.GetFields(); foreach (var field in fields) { var comparePropertyAttribute = (ComparePropertyFieldAttribute)field.GetCustomAttributes(typeof(ComparePropertyFieldAttribute), false).FirstOrDefault(); if (comparePropertyAttribute != null) { list.Add(Tuple.Create<object, ComparePropertyFieldAttribute>(field, comparePropertyAttribute)); } } dict.Add(type, list.ToArray()); } } var sb = new StringBuilder(200); //估計200字節能覆蓋大多數狀況了吧 var tupleArray = dict[type]; foreach (var tuple in tupleArray) { object v1 = null, v2 = null; if (tuple.Item1 is System.Reflection.PropertyInfo) { if (from != null) { v1 = ((System.Reflection.PropertyInfo)tuple.Item1).GetValue(from, null); } if (to != null) { v2 = ((System.Reflection.PropertyInfo)tuple.Item1).GetValue(to, null); } if (!object.Equals(v1, v2)) { sb.AppendFormat("{0}從 {1} 變成 {2}; ", tuple.Item2.PropertyName ?? ((System.Reflection.PropertyInfo)tuple.Item1).Name, v1 ?? "null", v2 ?? "null"); } } else if (tuple.Item1 is System.Reflection.FieldInfo) { if (from != null) { v1 = ((System.Reflection.FieldInfo)tuple.Item1).GetValue(from); } if (to != null) { v2 = ((System.Reflection.FieldInfo)tuple.Item1).GetValue(to); } if (!object.Equals(v1, v2)) { sb.AppendFormat("{0}從 {1} 變成 {2}; ", tuple.Item2.PropertyName ?? ((System.Reflection.FieldInfo)tuple.Item1).Name, v1 ?? "null", v2 ?? "null"); } } } differenceMsg = sb.ToString(); return differenceMsg == ""; } }
使用方法:優化
1. 將重要字段或屬性加上 [ComparePropertyField] 特性, 目前只支持C#基本類型, 好比 int, bool, string等, 你本身寫的class或者struct 須要重寫 ToString()、Equals(), 按理說若是重寫了Equals(), 那也須要重寫GetHashCode(), 但確實沒有用到GetHashCode(), 因此能夠忽略Warning不重寫GetHashCode()ui
2. 使用ComparePropertyFieldAttribute.CompareDifference 比較變動先後的實例便可this
具體可參考下面的示例spa
class Program { static void Main(string[] args) { // 請用Debug測試, Release會優化掉一些代碼致使測試不許確 System.Diagnostics.Stopwatch stopwatch = new Stopwatch(); var p1 = new Person() { INT = 1, BOOL = false, S = "p1", S2 = "p1" }; var p2 = new Person() { INT = 3, BOOL = false, S = "p1", S2 = "p1" }; string msg = null; stopwatch.Start(); for (int i = 0; i < 10000000; i++) { if (!p1.Equals(p2)) { msg = string.Format("{0} 變成 {1}", p1.ToString(), p2.ToString()); } } stopwatch.Stop(); Console.WriteLine("原生比較結果: " + msg); Console.WriteLine("原生比較耗時: " + stopwatch.Elapsed); stopwatch.Start(); for (int i = 0; i < 10000000; i++) { var result = ComparePropertyFieldAttribute.CompareDifference<Person>(p1, p2, out msg); } stopwatch.Stop(); Console.WriteLine("ComparePropertyAttribute比較結果: " + msg); Console.WriteLine("ComparePropertyAttribute比較: " + stopwatch.Elapsed); Console.ReadLine(); } } public class Person { [ComparePropertyField] public int INT { get; set; } [ComparePropertyFieldAttribute("布爾")] public bool BOOL { get; set; } [ComparePropertyFieldAttribute("字符串")] public string S { get; set; } [ComparePropertyFieldAttribute("S22222")] public string S2; public override bool Equals(object obj) { var another = obj as Person; if (another==null) { return false; } return this.INT == another.INT && this.BOOL == another.BOOL && this.S == another.S && this.S2 == another.S2; } public override string ToString() { return string.Format("i={0}, 布爾={1}, 字符串={2}, S22222={3}", INT, BOOL, S, S2); } }
耗時是原生的3倍, 考慮到只有記錄日誌才使用這個, 使用的機會不多, 對性能的損耗能夠認爲很是小.pwa
end