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

news2024/9/20 16:38:59

image

前言

嘿,大家好!如果你之前看过我分享的文章《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# 7个方法比较两个对象是否相等
  2. 闲话 .NET(7):.NET Core 能淘汰 .NET FrameWork 吗?
  3. 常用的 4 种 ORM 框架(EF Core,SqlSugar,FreeSql,Dapper)对比总结

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

image

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2141205.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LLVM PASS-PWN-前置

文章目录 参考环境搭建基础知识![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/dced705dcbb045ceb8df2237c9b0fd71.png)LLVM IR实例1. **.ll 格式&#xff08;人类可读的文本格式&#xff09;**2. **.bc 格式&#xff08;二进制格式&#xff09;**3. **内存表示** …

无心剑英译张九龄《望月怀远》

望月怀远 Watching the Moon and Missing You Far Away 张九龄 By Zhang Jiuling 海上生明月&#xff0c;天涯共此时 情人怨遥夜&#xff0c;竟夕起相思 灭烛怜光满&#xff0c;披衣觉露滋 不堪盈手赠&#xff0c;还寝梦佳期 The bright moon rises from the sea, So far apart…

【宠物小精灵之收服(待更新)】

题目 代码 #include <bits/stdc.h> using namespace std; int f[1010][510]; int main() {int n, m, k;cin >> n >> m >> k;int c 0;for(int i 1; i < k; i){int cost, hp;cin >> cost >> hp;for(int j n; j > cost; j--){for(i…

java技术栈介绍

Java技术栈是一个庞大而丰富的生态系统&#xff0c;它包含了从基础语言特性到高级框架、库和工具的整个集合。这个技术栈为开发者提供了构建各种类型应用&#xff08;包括企业级应用、Web应用、移动应用、大数据应用等&#xff09;所需的全部组件。以下是对Java技术栈的一个更详…

【webpack4系列】编写可维护的webpack构建配置(四)

文章目录 构建配置包设计功能模块设计和目录结构设计功能模块设计目录结构设计 使用ESLint规范构建脚本冒烟测试介绍和实际运用冒烟测试 (smoke testing)冒烟测试执行判断构建是否成功判断基本功能是否正常 单元测试和测试覆盖率测试框架编写单元测试用例单元测试接入测试覆盖率…

新发布!Streamlab X系列第二版:短视频电影直播全能主题,赋能苹果CMS

Streamlab X系列第二版强势登陆&#xff0c;专为苹果CMS设计的短视频与电影直播融合的多功能主题模板震撼首发&#xff01; 这款主题以其非凡的适应性和极致的视觉效果&#xff0c;重新定义了网站构建的边界。采用独家精心研发的框架&#xff0c;它能够无缝跨越从移动设备到超…

在实际LabVIEW开发中,哪些算法是常用的?

在LabVIEW的实际开发中&#xff0c;常用的算法主要集中在数据处理、控制系统、信号处理、图像处理等领域。以下是一些常用算法的介绍&#xff1a; 1. PID控制算法 PID&#xff08;比例-积分-微分&#xff09;控制是LabVIEW中常用的算法之一&#xff0c;广泛应用于工业自动化和…

【四】k8s部署 TDengine集群

k8s部署 TDengine集群 目录 k8s部署 TDengine集群 一、在 Kubernetes 上部署 TDengine 集群 第一步&#xff1a;创建命名空间 第二步&#xff1a;从yaml创建有状态服务 StatefulSet 第三步&#xff1a;配置 Service 服务 二、集群测试 一、在 Kubernetes 上部署 TDengine…

【数据结构-扫描线】力扣57. 插入区间

给你一个 无重叠的 &#xff0c;按照区间起始端点排序的区间列表 intervals&#xff0c;其中 intervals[i] [starti, endi] 表示第 i 个区间的开始和结束&#xff0c;并且 intervals 按照 starti 升序排列。同样给定一个区间 newInterval [start, end] 表示另一个区间的开始和…

Java数据存储结构——二叉查找树

文章目录 22.1.2二叉查找树22.1.2.1 概述22.1.2.1二叉查找树添加节点22.1.2.2二叉查找树查找节点22.1.2.3 二叉树遍历22.1.2.4 二叉查找树的弊端 22.1.2二叉查找树 22.1.2.1 概述 二叉查找树,又称二叉排序树或者二叉搜索树 二叉查找树的特点&#xff1a; 每一个节点上最多有…

25. 网格模型(三角形概念)

给大家演示网格模型Mesh渲染自定义几何体BufferGeometry的顶点坐标,通过这样一个例子帮助大家建立**三角形(面)**的概念 三角形(面) 网格模型Mesh其实就一个一个三角形(面)拼接构成。使用网格模型Mesh渲染几何体geometry&#xff0c;就是几何体所有顶点坐标三个为一组&#x…

【农信网-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

支持升降压型、升压、降压、60V的1.2MHz频率LED恒流驱动器LGS63040、LGS63042

前言&#xff1a; 一款支持升降压的LED驱动器。适合单节锂电池使用。当然不仅于此。SOT23-5封装的外形和丝印 特性 宽输入电压、宽输出电压范围&#xff1a;3.0V-60V 支持 PWM 调光及模拟调光 内置 60V/350mΩ低侧金属氧化物半导体场效应晶体管 1.2MHz固定工作频率 逐周期峰值…

MiniMaxi-共创智能新体验新手入门

新手快速入门 注册指南 个人用户 直接注册即可。 企业团队 主账号&#xff1a;注册时填写的姓名与手机号将成为企业账号的管理员。子账号&#xff1a;在用户中心创建&#xff0c;数量不限。 主账号与子账号权益 相同权益&#xff1a;子账号享有与主账号相同的使用权益和速…

积分电路和滤波电路的主要区别点和应用场合

文章目录 前言一、滤波电路的分类二、有源滤波器和无源滤波器的优缺点和实用范围三、积分电路3.1 无源积分电路3.2 RC充放电的电路响应3.2.1 RC电路的零状态响应3.2.2 RC电路的零输入响应3.2.3 RC电路的全响应3.2.4 选取合适的时间常数四 、无源RC低通滤波器4.3.1 截止频率推导…

AI 最佳实践全栈式从0到1开发个人博客系统

以下是「豆包 MarsCode 体验官」优秀文章&#xff0c;作者我喺小VIE。 前言 近年来随着人工智能&#xff08;AI&#xff09;大模型的迅猛发展&#xff0c;大模型在自然语言处理、计算机视觉、语音识别等领域的表现逐渐达到甚至超越人类的水平。在大模型的应用场景之下&#xf…

Gradio 中如何让 Chatbot 自动滚动

在 Gradio 中&#xff0c; Chatbot 是对话组件&#xff0c;接受 history 参数&#xff0c;在目前版本中 &#xff08;gradio4.44.0&#xff09;&#xff0c;不支持自动滚动&#xff0c;用起来很不方便&#xff0c;该功能在社区中已经提出了&#xff0c;目前该功能还没有发布。本…

Android Framework(六)WMS-窗口显示流程——窗口内容绘制与显示

文章目录 窗口显示流程明确目标 窗户内容绘制与显示流程窗口Surface状态完整流程图 应用端处理finishDrawingWindow 的触发 system_service处理WindowState状态 -- COMMIT_DRAW_PENDING本次layout 流程简述 窗口显示流程 目前窗口的显示到了最后一步。 在 addWindow 流程中&…

【ESP32】Arduino开发 | 中断矩阵+按键输入中断例程

对于中断矩阵的详细介绍会放在ESP-IDF开发文章中&#xff0c;跳转栏目目录可以找到对应文章。 1. API 1.1 绑定GPIO中断 attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode); pin&#xff1a;管脚号&#xff1b;handler&#xff1a;中断处理函数&#xff1b;mode…

【OJ刷题】双指针问题5

这里是阿川的博客&#xff0c;祝您变得更强 ✨ 个人主页&#xff1a;在线OJ的阿川 &#x1f496;文章专栏&#xff1a;OJ刷题入门到进阶 &#x1f30f;代码仓库&#xff1a; 写在开头 现在您看到的是我的结论或想法&#xff0c;但在这背后凝结了大量的思考、经验和讨论 目录 1…