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

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#中,反射是一种强大的特性,它允许在运行时检查和操作对象的类型信息。通过反射,我们可以获取类型的信息,创建实例,访问字段、属性、方法等。但是,反射并不能直接删除一个对象,因为对象的生命周期是由垃圾回收器(GC)管理的。使用反射可以做一些间接操作,比如设置对象的某些字段为null,从而有可能让垃圾回收器回收该对象,但这并不是直接删除对象。 要通过反射间接地让对象成为垃圾回收的候选,可以做以下操作: 1. 取消对该对象的所有引用。这样,对象就无法从代码中访问到了。 2. 如果对象是某个集合的成员,可以从集合中移除对该对象的引用。 下面是使用反射进行操作的一个简单例子: ```csharp using System; using System.Reflection; public class MyClass { public int Value { get; set; } } public class Program { public static void Main() { MyClass obj = new MyClass { Value = 10 }; Console.WriteLine("原始值: " + obj.Value); // 获取对象类型 Type objType = obj.GetType(); // 获取Value属性的字段信息 FieldInfo valueField = objType.GetField("value", BindingFlags.NonPublic | BindingFlags.Instance); if (valueField != null) { // 将Value字段设置为null valueField.SetValue(obj, null); } // 尝试输出Value的值,此时obj.Value已经是null Console.WriteLine("修改后的值: " + obj.Value); } } ``` 需要注意的是,取消对象引用或将其字段设置为null并不能保证对象马上被垃圾回收器回收,只能使对象成为可回收的状态。回收的实际时间取决于垃圾回收器的调度和执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值