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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Qt多元素控件——QTreeWidget

文章目录 QTreeWidgetQTreeWidget核心方法及信号QTreeWidgetItem核心属性及方法 QTreeWidget使用示例 QTreeWidget QTreeWidget表示树形控件&#xff0c;里面每个元素都是一个QTreeWidgetItem&#xff0c;每个QTreeWidgetItem可以包含多个文本和图标&#xff0c;每个文本/图标…

SEGGERS实时系统embOS推出Linux端模拟器

SEGGER 发布了两个新的 embOS 仿真模拟器&#xff1a;embOS Sim Linux 和 embOS-MPU Sim Linux。 通过模拟 Linux 主机系统上的硬件&#xff0c;取代物理硬件&#xff0c;为开发人员提供了一种无缝的方式来构建原型和测试应用程序。 embOS Sim Linux 端口支持 32 位和 64 位系…

第313题|解积分不等式题目的5种方法常用方法|武忠祥老师每日一题

解题思路&#xff1a;把多阶次积分和函数值联系起来&#xff0c;应该想到泰勒公式。 本题应该使用带有拉格朗日余项的泰勒公式&#xff1a; 方法一&#xff1a; 等式左右两边进行积分&#xff0c;右边第一项常数项不变&#xff0c;第二项&#xff08;x-1/2&#xff09;积完之…

371. 两整数之和

目录 一&#xff1a;题目&#xff1a; 二&#xff1a;代码&#xff1a; 三&#xff1a;结果&#xff1a; 一&#xff1a;题目&#xff1a; 给你两个整数 a 和 b &#xff0c;不使用 运算符 和 - &#xff0c;计算并返回两整数之和。 示例 1&#xff1a; 输入&#xff1a;…

★ C++进阶篇 ★ 多态

Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将继续和大家一起学习C进阶篇第一章----多态 ~ ❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️ 澄岚主页&#xff1a;椎名澄嵐-CSDN博客 C基础篇专栏&#xff1a;★ C基础篇 ★_椎名澄嵐的博客-CSDN博客 …

Gitea Action 简单配置(CI/CD)

线上pipeline,&#xff08;我使用是本地仓库的&#xff0c;你们使用切换成官网的即可&#xff09; # 工作流的名称name: Build and Push Docker Image deployment-k8s# 触发条件&#xff0c;只在 master 或 main 分支发送推送时触发 on:push:branches:- main# 作业&#xff0c…

深入理解Docke工作原理:UnionFS文件系统详解

在容器技术的世界中&#xff0c;文件系统的设计和实现是其关键组成部分&#xff0c;影响着镜像的构建效率、容器的启动速度以及资源的利用率。**UnionFS&#xff08;联合文件系统&#xff09;**作为Docker的核心文件系统技术&#xff0c;通过其独特的分层结构和写时复制&#x…

mac系统安装最新(截止2024.9.13)Oracle JDK操作记录

文章目录 下载JDK22配置环境变量验证环境变量是否生效整体命令如下 下载JDK22 打开最新版Oracle JDK下载地址 选择想要安装的JDK版本&#xff0c;然后选择适合兼容Mac机器的版本&#xff08;Intel/arm&#xff09;&#xff0c;建议直接下载安装程序&#xff0c;可视化安装 默…

网络安全实训八(y0usef靶机渗透实例)

1 信息收集 1.1 扫描靶机IP 1.2 收集靶机的端口开放情况 1.3 探测靶机网站的目录 1.4 发现可疑网站 1.5 打开可疑网站 2 渗透 2.1 使用BP获取请求 2.2 使用工具403bypasser.py探测可疑网页 2.3 显示可以添加头信息X-Forwarded-For:localhost来访问 2.4 添加之后转发&#xff…

Java入门,初识Java

Java背景知识 Java是早期美国 sun 公司&#xff08;Stanford University Network&#xff09;在1995年推出的一门计算机高级编程语言。Java早期称为Oak&#xff08;中文翻译为&#xff1a;橡树&#xff09;&#xff0c;后期改名为Java。&#xff08;因为当时sun公司门口有很多…

使用ENVI之辐射定标

将下载好的遥感影像导入遥感影像处理软件ENVI 5.6中 使用ENVI 5.6的Toolbox中的Radiometric Calibration工具 跳出的Date Input File界面中选中要进行辐射定标的文件选中 再在跳出的Radiometric Calibration界面中将Output Interleave改为BIL再点击Apply FLAASH Settings Soale…

初始爬虫7

针对数据提取的项目实战&#xff1a; 补充初始爬虫6的一个知识点&#xff1a; etree.tostring能够自动补全html缺失的标签&#xff0c;显示原始的HTML结构 # -*- coding: utf-8 -*- from lxml import etreetext <div> <ul> <li class"item-1">…

基于Boost库的搜索引擎开发实践

目录 1.项目相关背景2.宏观原理3.相关技术栈和环境4.正排、倒排索引原理5.去标签和数据清洗模块parser5.1.认识标签5.2.准备数据源5.3.编写数据清洗代码parser5.3.1.编写读取文件Readfile5.3.2.编写分析文件Anafile5.3.2.编写保存清洗后数据SaveHtml5.3.2.测试parser 6.编写索引…

STM32内部闪存FLASH(内部ROM)、IAP

1 FLASH简介 1 利用程序存储器的剩余空间来保存掉电不丢失的用户数据 2 通过在程序中编程(IAP)实现程序的自我更新 &#xff08;OTA&#xff09; 3在线编程&#xff08;ICP把整个程序都更新掉&#xff09; 1 系统的Bootloader写死了&#xff0c;只能用串口下载到指定的位置&a…

从基础到进阶:利用EasyCVR安防视频汇聚平台实现高效视频监控系统的五步走

随着科技的飞速发展&#xff0c;视频监控技术在社会安全、企业管理、智慧城市构建等领域扮演着越来越重要的角色。一个高效智能的视频监控管理系统不仅能够提升监控效率&#xff0c;还能在预防犯罪、事故预警、数据分析等方面发挥巨大作用。 一、需求分析 在设计视频监控管理…

sql中索引查看是否生效

在pg数据库中有多种索引存在&#xff0c;在一般情况下我们取使用普通索引 以下是一些常见导致索引未命中的原因和优化策略 1.如果查询中的条件与索引字段的顺序不匹配&#xff0c;或者索引字段没有完全包含在查询条件中&#xff0c;索引可能不会被使用。 2.在查询中使用函数…

【Python 数据分析学习】Matplotlib 的基础和应用

题目 1 Matplotlib 主要特性2 Matplotlib 基础知识2.1 导入模块2.2 图形构成2.2.1 图形&#xff08;Figure&#xff09;2.2.2 轴 &#xff08;Axes&#xff09;2.2.3 轴线&#xff08;axis&#xff09; 2.5 中文设置2.5.1 借助rcParams修改字体实现设置2.5.2 增加一个fontprope…

结构体的内存对齐

对⻬规则&#xff1a; 1.结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处 2.其他成员变量要对⻬到某个数字&#xff08;对⻬数&#xff09;的整数倍的地址处。 对⻬数编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。 但一些编译器下并没有默认对其数 3.结…

Python 解析 JSON 数据

1、有如下 JSON 数据&#xff0c;存放在 data.json 文件&#xff1a; [{"id":1, "name": "小王", "gender": "male", "score": 96.8}, {"id":2, "name": "小婷", "gender&qu…

【踩坑】装了显卡,如何让显示器从主板和显卡HDMI都输出

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 背景介绍 装了显卡后&#xff0c;开机默认是从显卡的HDMI输出&#xff0c;但这很不方便。如何让视频仍然从主板输出&#xff1f;或者说让显卡HDMI和主板…