欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-05-20 23:26:57
记录日志时, 经常需要描述对象的状态发生了怎样的变化, 以前处理的非常简单粗暴: a. 重写class的ToString()方法, 将重要的属性都输出来 b. 记录日志时: 谁谁谁 由 变更前实例.ToString() 变成 变更后实例.ToString() 但输出的日志总是太长了, 翻看日志时想找 ......

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

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

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

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

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

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

 

  使用方法:

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

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

  具体可参考下面的示例

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

 

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

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

 

  end