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


  記錄日志時, 經常需要描述對象的狀態發生了怎樣的變化, 以前處理的非常簡單粗暴:

  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

 

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM