記錄日誌時, 經常需要描述對象的狀態發生了怎樣的變化, 以前處理的非常簡單粗暴: a. 重寫class的ToString()方法, 將重要的屬性都輸出來 b. 記錄日誌時: 誰誰誰 由 變更前實例.ToString() 變成 變更後實例.ToString() 但輸出的日誌總是太長了, 翻看日誌時想找 ...
記錄日誌時, 經常需要描述對象的狀態發生了怎樣的變化, 以前處理的非常簡單粗暴:
a. 重寫class的ToString()方法, 將重要的屬性都輸出來
b. 記錄日誌時: 誰誰誰 由 變更前實例.ToString() 變成 變更後實例.ToString()
但輸出的日誌總是太長了, 翻看日誌時想找到差異也非常麻煩, 所以想輸出為: 誰誰誰的哪個屬性由 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 == ""; } }ComparePropertyFieldAttribute
使用方法:
1. 將重要欄位或屬性加上 [ComparePropertyField] 特性, 目前只支持C#基本類型, 比如 int, bool, string等, 你自己寫的class或者struct 需要重寫 ToString()、Equals(), 按理說如果重寫了Equals(), 那也需要重寫GetHashCode(), 但確實沒有用到GetHashCode(), 所以可以忽略Warning不重寫GetHashCode()
2. 使用ComparePropertyFieldAttribute.CompareDifference 比較變更前後的實例即可
具體可參考下麵的示例
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); } }View Code
耗時是原生的3倍, 考慮到只有記錄日誌才使用這個, 使用的機會很少, 對性能的損耗可以認為非常小.
end