C# 比较对象新思路,利用反射技术打造更灵活的比较工具

简介: 中途接手的项目,碰到需要在更新对象信息时比较并记录差异的需求,最变态的还有附加要求,怎么办?有没有既能满足需求又能对项目影响最小的方法呢?分享这个我封装的方法,一个利用反射技术打造的更灵活的比较工具

Coding-91.png

前言

嘿,大家好!如果你之前看过我分享的文章《C# 7个方法比较两个对象是否相等》,你可能会意识到对象比较在实际业务中经常出现的场景。今天,我想继续与大家分享一个在实际项目中遇到的问题。

有一次,我接手了一个别人的项目,有个新的需求,需要在更新对象信息时比较并记录差异,方便以后跟踪溯源,但我不太想修改底层的实体类,因为这可能会带来不可知的影响,这样会增加很多测试的工作量,而且,我一时也找不到适合的第三方库,因为需求要求同时把实体类的描述特性的内容一起写入数据表。

有没有一种方法,既能满足需求,又能对项目影响最小呢?

最后,我在反射技术上找到了灵感,手动造了一个轮子,封装了一个利用反射来比较对象的方法。这个方法不仅灵活,还能减少对项目的侵入性,非常适合在实际项目中使用,所以分享给大家,一起来看看吧!

封装对象比较方法

创建一个 CommonUtil 静态类,在里面编写封装方法,代码如下,留意注释

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace Common.Util
{
   
    /// <summary>
    /// 公共工具静态类
    /// </summary>
    public static class CommonUtil
    {
   
        /// <summary>
        /// 获取指定类型的属性的 descriptionattribute 特性的值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <param name="PropertyName"></param>
        /// <returns></returns>
        public static string GetClassDescriptionAttributeValue<T>(T t, string PropertyName)
        {
   
            AttributeCollection attributes = TypeDescriptor.GetProperties(t)[PropertyName].Attributes;
            DescriptionAttribute descriptionAttribute = (DescriptionAttribute)attributes[typeof(DescriptionAttribute)];
            return descriptionAttribute.Description;
        }

        /// <summary>
        /// 比较同一个类型的两个对象的属性的值是否相同,
        /// 如果不相同,执行一个自定义的 Action
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="obj1">旧的对象</param>
        /// <param name="obj2">新的对象</param>
        /// <param name="action">自定义动作</param>
        /// <param name="excludePropertys">要忽略比较的字段,默认为空</param>
        public static void CompareClassObjectPropertys<T>(T obj1, T obj2, Action<string, string , string, string> action, string[] excludePropertys = null)
        {
   
            // 反射获取对象的所有属性
            PropertyInfo[] properties = obj1.GetType().GetProperties();
            foreach (var po in properties)
            {
   
                var propertyName = po.Name;

                // 跳过忽略比较的字段
                if (excludePropertys != null && excludePropertys.Length > 0)
                {
   
                    if (excludePropertys.Contains(propertyName)) continue;
                }

                // 分别获取两个对象的属性的值
                var oldValueObj = po.GetValue(obj1, null);
                var newValueObj = po.GetValue(obj2, null);
                if (oldValueObj == null && newValueObj == null)
                {
   
                    continue;
                }

                var oldValue = TranNull<string>(oldValueObj);
                var newValue = TranNull<string>(newValueObj);
                var propertyDesc = GetClassDescriptionAttributeValue<T>(obj1, propertyName);
                if (!oldValue.Equals(newValue))
                {
   
                    // 如果字段值不相同,执行自定义操作,比如保存到数据库
                    action(propertyName, propertyDesc, oldValue, newValue);
                }
            }
        }
    }
}

使用步骤

  1. 创建两个实体类,里面的属性有描述特性,代码如下:

     using System;
     using System.Collections.Generic;
     using System.Linq;
     using System.Text;
     using System.Threading.Tasks;
     using System.ComponentModel.DataAnnotations;
     using System.ComponentModel;
    
     namespace BLL.Model.ViewModels
     {
         
         /// <summary>
         /// 企业信息表视图模型
         /// </summary>
         public class ComViewModel
         {
         
             [Description("公司码")]
             public string COMP {
          get; set; }
    
             [Description("公司名称")]
             public string COMPNAME {
          get; set; }
    
             [Description("纳税人识别号")]
             public string TAXNO {
          get; set; }
    
             [Description("地址")]
             public string ADRF1 {
          get; set; }
    
             [Description("邮政编码")]
             public string PCOD {
          get; set; }
    
             [Description("电话")]
             public string PHN {
          get; set; }
    
             [Description("递送地址")]
             public string DELADRF {
          get; set; }
    
             [Description("递送邮政编码")]
             public string DELPCOD {
          get; set; }
    
             [Description("开户银行")]
             public string BANK {
          get; set; }
    
             [Description("银行账号")]
             public string BNKAC {
          get; set; }
    
             [Description("电邮")]
             public string EMAL {
          get; set; }
    
             [Description("网站")]
             public string WEB {
          get; set; }
    
             [Description("主要联络人")]
             public string CONTA {
          get; set; }
    
             [Description("状态")]
             public string STS {
          get; set; }
    
             [Description("更新用户")]
             public string USER {
          get; set; }
    
             [Description("更新时间")]
             public System.DateTime LMDTM {
          get; set; }
         }
    
         /// <summary>
         /// HTFP01H 表视图模型
         /// </summary>
         public class ComUpLogViewModel
         {
         
             public int LNID {
          get; set; }
    
             [Description("字段名")]
             public string FLD {
          get; set; }
    
             [Description("字段描述")]
             public string DESC {
          get; set; }
    
             [Description("旧值")]
             public string OLD {
          get; set; }
    
             [Description("新值")]
             public string ADRF {
          get; set; }
    
             [Description("更新用户")]
             public string USER {
          get; set; }
    
             [Description("更新时间")]
             public System.DateTime LMDTM {
          get; set; }
         }
     }
    
  2. 在更新数据时比较对象并记录差异

     using Common.Util;
    
     /// <summary>
     /// 更新数据私有方法
     /// </summary>
     /// <param name="oldComViewModel"></param>
     /// <param name="newComViewModel"></param>
     /// <returns></returns>
     private bool Update(ComViewModel oldComViewModel, ComViewModel newComViewModel)
     {
         
         // 1. 比较新旧两个 ViewModel 类的不同,将更改的信息放到一个集合中
         var list = new List<ComUpLog>();
         var excludeProperys = new string[9] {
          "USERHT01", "LMDTMHT01" };
         CommonUtil.CompareClassObjectPropertys<ComViewModel>(
             oldComViewModel,
             newComViewModel,
             (propertyName, propertyDesc, oldValue, newValue) => {
         
                 var ComUpLog = new ComUpLog()
                 {
         
                     FLD = $"[{oldComViewModel.COMP}]{propertyName}",
                     DESC = propertyDesc,
                     OLD = oldValue,
                     ADRF = newValue,
                     USER = newComViewModel.USERHT01,
                     LMDTM = DateTime.Now
                 };
                 list.Add(ComUpLog);
             }, excludeProperys);
    
         // 2. EF 批量更新数据到数据库
         ……
     }
    

总结

通过反射技术,我们能够轻松获取对象的属性及其值,从而实现两个对象之间的比较。

这种方式非常灵活,可以达到“非侵入”的效果,对原有项目的影响降到最低。

使用这个封装的方法,你只需根据实际需求稍作修改,就能在项目中更灵活地进行对象比较。

希望这个小技巧能为你的项目带来便利!🌟

最后,如果您有更好的建议和不同的观点,欢迎留言讨论!

往期精彩

  1. C# 静态类,高手不想让你知道的 15 个真相
  2. 封装一个 C# 范围判断函数,从此告别重复编写范围判断代码的烦恼
  3. 用 C# Stopwatch 计时,让代码性能飞起来!
  4. 轻装上阵,Visual Studio LocalDB:.NET 程序员的本地数据库神器
  5. 封装一个C#万能基础数据类型转换器,一招解决所有基础类型转换烦恼
  6. 闲话 .NET(7):.NET Core 能淘汰 .NET FrameWork 吗?
  7. 常用的 4 种 ORM 框架(EF Core,SqlSugar,FreeSql,Dapper)对比总结
  8. C# AutoMapper 10个常用方法总结
  9. C# 7个方法比较两个对象是否相等
  10. C# 去掉字符串最后一个字符的 4 种方法

我是老杨,一个执着于编程乐趣、至今奋斗在一线的 10年+ 资深研发老鸟,是软件项目管理师,也是快乐的程序猿,持续免费分享全栈实用编程技巧、项目管理经验和职场成长心得。欢迎关注老杨的公众号(名称:代码掌控者),更多干货等你来!

相关文章
|
20天前
|
Web App开发 Linux C#
C# 网页截图全攻略:三种技术与 Chrome 路径查找指南
本文主要介绍了在 C# 中实现网页截图的几种技术及相关要点。涵盖了 PuppeteerSharp、Selenium 和 HtmlToImage 三种方式,分别阐述了它们的安装步骤及核心代码。同时,针对在 C# 中寻找 Windows 上 chrome.exe 路径这一问题,分析了未安装 Google Chrome 和已安装两种情况下的查找原因,并给出了相关参考链接,还列举了一系列与 C# 使用 Selenium、获取 chrome.exe 路径以及在 Linux 上部署相关的参考资料。
56 11
|
2月前
|
开发框架 算法 .NET
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
|
2月前
|
开发框架 Cloud Native .NET
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
|
3月前
|
人工智能 开发框架 前端开发
C#/.NET/.NET Core技术前沿周刊 | 第 12 期(2024年11.01-11.10)
C#/.NET/.NET Core技术前沿周刊 | 第 12 期(2024年11.01-11.10)
|
3月前
|
JSON 程序员 C#
使用 C# 比较两个对象是否相等的7个方法总结
比较对象是编程中的一项基本技能,在实际业务中经常碰到,比如在ERP系统中,企业的信息非常重要,每一次更新,都需要比较记录更新前后企业的信息,直接比较通常只能告诉我们它们是否指向同一个内存地址,那我们应该怎么办呢?分享 7 个方法给你!
|
3月前
|
人工智能 开发框架 安全
C#/.NET/.NET Core技术前沿周刊 | 第 13 期(2024年11.11-11.17)
C#/.NET/.NET Core技术前沿周刊 | 第 13 期(2024年11.11-11.17)
|
4月前
|
人工智能 开发框架 Cloud Native
C#/.NET/.NET Core技术前沿周刊 | 第 9 期(2024年10.07-10.13)
C#/.NET/.NET Core技术前沿周刊 | 第 9 期(2024年10.07-10.13)
|
3月前
|
C# 开发者
C# 一分钟浅谈:Code Contracts 与契约编程
【10月更文挑战第26天】本文介绍了 C# 中的 Code Contracts,这是一个强大的工具,用于通过契约编程增强代码的健壮性和可维护性。文章从基本概念入手,详细讲解了前置条件、后置条件和对象不变量的使用方法,并通过具体代码示例进行了说明。同时,文章还探讨了常见的问题和易错点,如忘记启用静态检查、过度依赖契约和性能影响,并提供了相应的解决建议。希望读者能通过本文更好地理解和应用 Code Contracts。
59 3
|
2月前
|
存储 安全 编译器
学懂C#编程:属性(Property)的概念定义及使用详解
通过深入理解和使用C#的属性,可以编写更清晰、简洁和高效的代码,为开发高质量的应用程序奠定基础。
112 12
|
3月前
|
设计模式 C# 图形学
Unity 游戏引擎 C# 编程:一分钟浅谈
本文介绍了在 Unity 游戏开发中使用 C# 的基础知识和常见问题。从 `MonoBehavior` 类的基础用法,到变量和属性的管理,再到空引用异常、资源管理和性能优化等常见问题的解决方法。文章还探讨了单例模式、事件系统和数据持久化等高级话题,旨在帮助开发者避免常见错误,提升游戏开发效率。
108 4