C#使用反射获取对象变化的情况

时间:2022-10-26 21:18:35

  记录日志时, 经常需要描述对象的状态发生了怎样的变化, 以前处理的非常简单粗暴:

  a. 重写class的ToString()方法, 将重要的属性都输出来

  b. 记录日志时:  谁谁谁  由  变更前实例.ToString()   变成   变更后实例.ToString()

  但输出的日志总是太长了, 翻看日志时想找到差异也非常麻烦, 所以想输出为:  谁谁谁的哪个属性由  aaa 变成了 bbb

  手写代码一个一个的比较字段然后输出这样的日志信息, 是不敢想象的事情. 本来想参考Dapper使用 System.Reflection.Emit 发射 来提高运行效率, 但实在没有功夫研究.Net Framework的中间语言, 所以准备用 Attribute特性 和 反射 来实现

C#使用反射获取对象变化的情况
C#使用反射获取对象变化的情况
/// <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 == "";
    }
}
C#使用反射获取对象变化的情况

 

  使用方法:

  1. 将重要字段或属性加上 [ComparePropertyField] 特性, 目前只支持C#基本类型, 比如 int, bool, string等, 你自己写的class或者struct 需要重写 ToString()、Equals(), 按理说如果重写了Equals(), 那也需要重写GetHashCode(), 但确实没有用到GetHashCode(), 所以可以忽略Warning不重写GetHashCode()

  2. 使用ComparePropertyFieldAttribute.CompareDifference 比较变更前后的实例即可

  具体可参考下面的示例

C#使用反射获取对象变化的情况
C#使用反射获取对象变化的情况
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);
    }
}
C#使用反射获取对象变化的情况

 

 C#使用反射获取对象变化的情况

 耗时是原生的3倍, 考虑到只有记录日志才使用这个, 使用的机会很少, 对性能的损耗可以认为非常小.

 

  end