You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中为含List属性的类实现HasChanged状态追踪异常问题

对象列表状态变更检测问题解决方案

问题描述

我正在将数据集(Dataset)转换为类对象列表,想要模仿DataTable的HasChanged方法,通过对象追踪检测列表中对象的新增、修改或删除操作。但当前实现存在以下问题:

  • 调用SetOriginalState保存初始状态后,HasChanged返回true(预期应为false)
  • 修改对象属性后,HasChanged仍返回true
    经排查,当前对象与originalState的属性值完全一致,无法定位原因,寻求解决方案。

相关代码

VendorInvoiceData Model

public class VendorInvoiceData
{
    public VendorInvoiceData() { }
    
    public VendorInvoiceData(VendorInvoiceData vendorInvoiceData)
    {
        VendorInvoiceDataId = vendorInvoiceData.VendorInvoiceDataId;
        VendorInvoiceId = vendorInvoiceData.VendorInvoiceId;
        ChargeId = vendorInvoiceData.ChargeId;
        ChargeCode = vendorInvoiceData.ChargeCode;
        InvoiceAmount = vendorInvoiceData.InvoiceAmount;
        CreatedBy = vendorInvoiceData.CreatedBy;
        CreationDate = vendorInvoiceData.CreationDate;
        SpreadGroup = vendorInvoiceData.SpreadGroup;
        SpreadOrder = vendorInvoiceData.SpreadOrder;
    }

    public int VendorInvoiceDataId { get; set; }
    public int? VendorInvoiceId { get; set; }
    public int? ChargeId { get; set; }
    public string ChargeCode { get; set; }
    public decimal? InvoiceAmount { get; set; }
    public string CreatedBy { get; set; }
    public DateTime? CreationDate { get; set; }
    public string SpreadGroup { get; set; }
    public short? SpreadOrder { get; set; }
}

VendorInvoiceStatusLog Model

public class VendorInvoiceStatusLog
{
    public VendorInvoiceStatusLog() { }

    public VendorInvoiceStatusLog(VendorInvoiceStatusLog vendorInvoiceStatusLog)
    {
        VendorInvoiceStatusLogId = vendorInvoiceStatusLog.VendorInvoiceStatusLogId;
        VendorInvoiceId = vendorInvoiceStatusLog.VendorInvoiceId;
        VendorInvoiceStatus = vendorInvoiceStatusLog.VendorInvoiceStatus;
        VendorInvoiceStatusDescription = vendorInvoiceStatusLog.VendorInvoiceStatusDescription;
        CreatedBy = vendorInvoiceStatusLog.CreatedBy;
        CreationDate = vendorInvoiceStatusLog.CreationDate;
        IsAdded = vendorInvoiceStatusLog.IsAdded;
        IsUpdated = vendorInvoiceStatusLog.IsUpdated;
        IsDeleted = vendorInvoiceStatusLog.IsDeleted;
    }

    public int VendorInvoiceStatusLogId { get; set; }
    public int? VendorInvoiceId { get; set; }
    public string VendorInvoiceStatus { get; set; }
    public string VendorInvoiceStatusDescription { get; set; }
    public string CreatedBy { get; set; }
    public DateTime? CreationDate { get; set; }
    public bool IsAdded { get; set; } = false;
    public bool IsUpdated { get; set; } = false;
    public bool IsDeleted { get; set; } = false;
}

VendorInvoiceModelsList Model

public class VendorInvoiceModelsList
{
    public List<VendorInvoiceStatusLog> VendorInvoiceStatusLogList { get; set; }
    public List<VendorInvoiceData> VendorInvoiceDataList { get; set; }

    public VendorInvoiceModelsList originalState;

    public void SetOriginalState()
    {
        originalState = new VendorInvoiceModelsList
        {
            VendorInvoiceStatusLogList = VendorInvoiceStatusLogList.Select(x => new VendorInvoiceStatusLog(x)).ToList(),
            VendorInvoiceDataList = VendorInvoiceDataList.Select(x => new VendorInvoiceData(x)).ToList(),
        };
    }

    public bool HasChanged()
    {
        if (originalState == null) return false;

        bool statusLog = !VendorInvoiceStatusLogList.SequenceEqual(originalState.VendorInvoiceStatusLogList);
        bool data = !VendorInvoiceDataList.SequenceEqual(originalState.VendorInvoiceDataList);

        return statusLog || data;
    }
}

使用代码

VendorInvoiceModelsList vendorInvoiceModels = Find(vendorInvoiceId);
vendorInvoiceModels.SetOriginalState();
var data = vendorInvoiceModels.HasChanged(); // 返回true(预期false)
vendorInvoiceModels.VendorInvoiceStatusLogList[0].VendorInvoiceStatus = "newStatus"; // 修正原代码中的属性名错误
var data1 = vendorInvoiceModels.HasChanged(); // 返回true

问题根源

SequenceEqual方法对于引用类型,默认比较的是对象的引用地址,而非属性值。你的模型类是引用类型,即使通过复制构造函数创建了属性值完全一致的新对象,它们的引用地址仍然不同,因此SequenceEqual会判定两个对象不相等,导致HasChanged始终返回true。

解决方案

方案1:重写模型类的Equals和GetHashCode方法

为每个模型类实现基于属性值的相等比较逻辑:

修改VendorInvoiceData类

public class VendorInvoiceData
{
    // 原有构造函数和属性不变...

    public override bool Equals(object obj)
    {
        if (obj is not VendorInvoiceData other)
            return false;

        return VendorInvoiceDataId == other.VendorInvoiceDataId
            && VendorInvoiceId == other.VendorInvoiceId
            && ChargeId == other.ChargeId
            && ChargeCode == other.ChargeCode
            && InvoiceAmount == other.InvoiceAmount
            && CreatedBy == other.CreatedBy
            && CreationDate == other.CreationDate
            && SpreadGroup == other.SpreadGroup
            && SpreadOrder == other.SpreadOrder;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(VendorInvoiceDataId, VendorInvoiceId, ChargeId, ChargeCode, InvoiceAmount, CreatedBy, CreationDate, SpreadGroup, SpreadOrder);
    }
}

修改VendorInvoiceStatusLog类

public class VendorInvoiceStatusLog
{
    // 原有构造函数和属性不变...

    public override bool Equals(object obj)
    {
        if (obj is not VendorInvoiceStatusLog other)
            return false;

        return VendorInvoiceStatusLogId == other.VendorInvoiceStatusLogId
            && VendorInvoiceId == other.VendorInvoiceId
            && VendorInvoiceStatus == other.VendorInvoiceStatus
            && VendorInvoiceStatusDescription == other.VendorInvoiceStatusDescription
            && CreatedBy == other.CreatedBy
            && CreationDate == other.CreationDate
            && IsAdded == other.IsAdded
            && IsUpdated == other.IsUpdated
            && IsDeleted == other.IsDeleted;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(VendorInvoiceStatusLogId, VendorInvoiceId, VendorInvoiceStatus, VendorInvoiceStatusDescription, CreatedBy, CreationDate, IsAdded, IsUpdated, IsDeleted);
    }
}

方案2:使用自定义IEqualityComparer

如果不想修改模型类,可以创建自定义的相等比较器,在SequenceEqual中指定使用:

创建VendorInvoiceDataEqualityComparer

public class VendorInvoiceDataEqualityComparer : IEqualityComparer<VendorInvoiceData>
{
    public bool Equals(VendorInvoiceData x, VendorInvoiceData y)
    {
        if (x == null && y == null) return true;
        if (x == null || y == null) return false;

        return x.VendorInvoiceDataId == y.VendorInvoiceDataId
            && x.VendorInvoiceId == y.VendorInvoiceId
            && x.ChargeId == y.ChargeId
            && x.ChargeCode == y.ChargeCode
            && x.InvoiceAmount == y.InvoiceAmount
            && x.CreatedBy == y.CreatedBy
            && x.CreationDate == y.CreationDate
            && x.SpreadGroup == y.SpreadGroup
            && x.SpreadOrder == y.SpreadOrder;
    }

    public int GetHashCode(VendorInvoiceData obj)
    {
        return HashCode.Combine(obj.VendorInvoiceDataId, obj.VendorInvoiceId, obj.ChargeId, obj.ChargeCode, obj.InvoiceAmount, obj.CreatedBy, obj.CreationDate, obj.SpreadGroup, obj.SpreadOrder);
    }
}

创建VendorInvoiceStatusLogEqualityComparer

public class VendorInvoiceStatusLogEqualityComparer : IEqualityComparer<VendorInvoiceStatusLog>
{
    public bool Equals(VendorInvoiceStatusLog x, VendorInvoiceStatusLog y)
    {
        if (x == null && y == null) return true;
        if (x == null || y == null) return false;

        return x.VendorInvoiceStatusLogId == y.VendorInvoiceStatusLogId
            && x.VendorInvoiceId == y.VendorInvoiceId
            && x.VendorInvoiceStatus == y.VendorInvoiceStatus
            && x.VendorInvoiceStatusDescription == y.VendorInvoiceStatusDescription
            && x.CreatedBy == y.CreatedBy
            && x.CreationDate == y.CreationDate
            && x.IsAdded == y.IsAdded
            && x.IsUpdated == y.IsUpdated
            && x.IsDeleted == y.IsDeleted;
    }

    public int GetHashCode(VendorInvoiceStatusLog obj)
    {
        return HashCode.Combine(obj.VendorInvoiceStatusLogId, obj.VendorInvoiceId, obj.VendorInvoiceStatus, obj.VendorInvoiceStatusDescription, obj.CreatedBy, obj.CreationDate, obj.IsAdded, obj.IsUpdated, obj.IsDeleted);
    }
}

修改HasChanged方法

public bool HasChanged()
{
    if (originalState == null) return false;

    bool statusLog = !VendorInvoiceStatusLogList.SequenceEqual(originalState.VendorInvoiceStatusLogList, new VendorInvoiceStatusLogEqualityComparer());
    bool data = !VendorInvoiceDataList.SequenceEqual(originalState.VendorInvoiceDataList, new VendorInvoiceDataEqualityComparer());

    return statusLog || data;
}

额外说明

  • 原使用代码中的vendorInvoiceModels.VendorInvoiceList[0]是笔误,应改为VendorInvoiceStatusLogList或VendorInvoiceDataList
  • 两种方案都能解决问题,方案1更通用(所有需要比较对象相等的场景都能复用),方案2更灵活(不需要修改模型类)

内容的提问来源于stack exchange,提问作者Abhishek Singh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 17:59:59