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

如何正确使用C#自动化测试中的AreEqual方法实现自定义类相等校验

C# 大型自定义类相等校验的优雅实现方案

以下是三种适配不同场景的解决方案,可替代手动逐字段编写Equals的实现:

方案1:使用record类型(C# 9+ 版本支持)

  • 适用场景:项目支持C# 9及以上版本,且MainMemento为纯数据承载类(备忘录模式的存储类天然符合该特征)
  • 实现优势:编译器会自动为record类型生成值相等逻辑,递归比较所有属性的取值,新增属性时不需要手动修改比较逻辑,代码量最少
  • 实现方式:仅需将类声明修改为record,删除自定义的Equals和GetHashCode方法即可,示例:
[Serializable]
public record MainMemento {
    public StateMemento StateMap { get; set; }
    public DepartmentEffectsMemento DepartmentEffects { get; set; }
    public GeneralDatabaseMemento<DocumentInstanceID, DocumentInstance> DocumentDB { get; set; }
    public GeneralDatabaseMemento<BuildingID, Building> BuildingDB { get; set; }
    public GeneralDatabaseMemento<CityActionID, CityAction> CityActionDB { get; set; }
    public int PlayerWeekToSkip { get; set; }
    public DocumentsHandlerMemento DocumentsHandler { get; set; }
    public List<KeyValuePair<GameLaws.Law, LawStatus>> Laws { get; set; }
    public Boolean SimulationState { get; set; }
    public Session.Settings Settings { get; set; }
    public ElementsPoolMemento<SaveableSprite> portraitPool { get; set; }
}

测试代码不需要做任何修改,原有Assert.AreEqual可正常执行。

方案2:通用反射深度比较工具

  • 适用场景:无法使用高版本C#的项目,且生产代码中需要频繁比较该类实例的相等性
  • 实现优势:一次实现可复用给所有数据类,不需要为每个类单独编写逐字段比较逻辑,支持嵌套类型、集合类型的递归比较
  • 实现示例:
using System.Reflection;
using System.Collections;

public static class DeepComparison
{
    public static bool DeepEquals<T>(T a, T b)
    {
        // 引用相等直接返回true
        if (ReferenceEquals(a, b)) return true;
        // 空值判断
        if (ReferenceEquals(a, null) || ReferenceEquals(b, null)) return false;
        Type type = typeof(T);
        // 值类型、字符串直接比较
        if (type.IsValueType || type == typeof(string)) return a.Equals(b);
        // 集合类型逐元素比较
        if (typeof(IEnumerable).IsAssignableFrom(type))
        {
            var enumA = ((IEnumerable)a).Cast<object>();
            var enumB = ((IEnumerable)b).Cast<object>();
            // 先判断长度是否相等
            if (enumA.Count() != enumB.Count()) return false;
            return enumA.Zip(enumB, DeepEquals).All(x => x);
        }
        // 引用类型遍历所有公开实例属性比较
        foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            var valA = prop.GetValue(a);
            var valB = prop.GetValue(b);
            if (!DeepEquals(valA, valB)) return false;
        }
        return true;
    }

    // 配套的GetHashCode生成方法,若需要用哈希容器可调用
    public static int GetDeepHashCode<T>(T obj)
    {
        if (ReferenceEquals(obj, null)) return 0;
        Type type = typeof(T);
        if (type.IsValueType || type == typeof(string)) return obj.GetHashCode();
        var hashCode = new HashCode();
        if (typeof(IEnumerable).IsAssignableFrom(type))
        {
            foreach (var item in (IEnumerable)obj)
            {
                hashCode.Add(GetDeepHashCode(item));
            }
            return hashCode.ToHashCode();
        }
        foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            hashCode.Add(GetDeepHashCode(prop.GetValue(obj)));
        }
        return hashCode.ToHashCode();
    }
}

使用时可以直接在测试中调用Assert.IsTrue(DeepComparison.DeepEquals(data.Item1, data.Item2)),也可以将自定义Equals方法替换为调用该工具类,避免逐字段编码。

方案3:测试侧结构化断言

  • 适用场景:仅测试场景需要比较相等性,生产代码不需要重写类的Equals逻辑,无需修改现有业务类代码
  • 实现优势:无需修改MainMemento的任何实现,支持灵活配置比较规则,比如忽略特定字段、自定义某类属性的比较逻辑
  • 实现示例:
[Test]
public void TestMainMementoComparison() {
    Tuple<MainMemento, MainMemento> data = SaveLoad();
    // 直接使用断言库的深度比较能力,不需要修改业务类代码
    Assert.That(data.Item1, Is.EqualTo(data.Item2).UsingDeepComparison());
}

主流的.NET测试断言库都原生支持该能力,可直接使用。


注意事项

你当前的实现存在逻辑风险:自定义的Equals方法和GetHashCode逻辑不一致,若将MainMemento作为字典Key、存入HashSet等哈希容器时会出现逻辑错误,若保留现有自定义Equals实现,必须同步重写GetHashCode方法,基于所有参与比较的属性计算哈希值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 19:21:00