C#使用反射獲取對象變化的狀況

  記錄日誌時, 常常須要描述對象的狀態發生了怎樣的變化, 之前處理的很是簡單粗暴:緩存

  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 == "";
    }
}
ComparePropertyFieldAttribute

 

  使用方法:優化

  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);
    }
}
View Code

 

 

 耗時是原生的3倍, 考慮到只有記錄日誌才使用這個, 使用的機會不多, 對性能的損耗能夠認爲很是小.pwa

 

  end

相關文章
相關標籤/搜索