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

无需重写Equals方法,按值比较C#引用类型对象

解决方案:无需重写Equals/GetHashCode的引用类型值比较

针对你需要在特定场景下对引用类型做值比较、默认保留引用比较的需求,以下是两种无需修改现有基类/子类结构的可行方案:

方案1:反射实现通用值比较器

优点:完全无需改动现有代码结构,直接调用工具类即可;缺点:性能略低于手动实现,适合非性能敏感场景。

编写静态工具类,通过反射遍历对象的公共字段和继承自基类的成员,实现值比较与哈希码生成:

using System;
using System.Reflection;

public static class ValueComparer
{
    public static bool EqualsByValue(object? obj1, object? obj2)
    {
        if (ReferenceEquals(obj1, obj2)) return true;
        if (obj1 == null || obj2 == null) return false;
        if (obj1.GetType() != obj2.GetType()) return false;

        // 遍历所有公共字段(含基类)
        foreach (var field in obj1.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
        {
            var value1 = field.GetValue(obj1);
            var value2 = field.GetValue(obj2);
            if (!Equals(value1, value2)) return false;
        }

        // 遍历所有公共属性(含基类,跳过索引器)
        foreach (var prop in obj1.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
        {
            if (prop.GetIndexParameters().Length > 0) continue;
            var value1 = prop.GetValue(obj1);
            var value2 = prop.GetValue(obj2);
            if (!Equals(value1, value2)) return false;
        }

        return true;
    }

    public static int GetHashCodeByValue(object obj)
    {
        if (obj == null) return 0;
        unchecked
        {
            int hash = 17;
            // 计算字段哈希
            foreach (var field in obj.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
            {
                var value = field.GetValue(obj);
                hash = hash * 23 + (value?.GetHashCode() ?? 0);
            }
            // 计算属性哈希(跳过索引器)
            foreach (var prop in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
            {
                if (prop.GetIndexParameters().Length > 0) continue;
                var value = prop.GetValue(obj);
                hash = hash * 23 + (value?.GetHashCode() ?? 0);
            }
            return hash;
        }
    }
}

修改你的Subway类,在需要值比较的位置调用工具类:

public class Subway {
    // 原有字段保留
    
    public override bool Equals(object? obj)
    {
        var otherSubway = obj as Subway;
        return this.Equals(otherSubway);
    }
    
    public bool Equals(Subway? otherSubway)
    {
        if (otherSubway == null) return false;
        
        return ValueComparer.EqualsByValue(this.oldestPassengerOnBoard, otherSubway.oldestPassengerOnBoard) &&
               otherSubway.model == model &&
               otherSubway.line == line &&
               otherSubway.constructionDate == constructionDate &&
               otherSubway.speed == speed;
    }
    
    public override int GetHashCode()
    {
        unchecked 
        {
            int hash = 7;
            hash = hash * 11 + ValueComparer.GetHashCodeByValue(oldestPassengerOnBoard);
            hash = hash * 11 + speed.GetHashCode();
            hash = hash * 11 + model.GetHashCode();
            hash = hash * 11 + line.GetHashCode();
            hash = hash * 11 + constructionDate.GetHashCode();
            return hash;
        }        
    }
}

方案2:表达式树编译优化版本

如果对性能要求较高,用表达式树预先编译每个类型的比较逻辑,避免重复反射开销,性能接近手动实现:

using System;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;

public static class CompiledValueComparer
{
    private static readonly ConcurrentDictionary<Type, Func<object?, object?, bool>> _equalsCache = new();
    private static readonly ConcurrentDictionary<Type, Func<object, int>> _hashCodeCache = new();

    public static bool EqualsByValue(object? obj1, object? obj2)
    {
        if (ReferenceEquals(obj1, obj2)) return true;
        if (obj1 == null || obj2 == null) return false;
        var type = obj1.GetType();
        if (type != obj2.GetType()) return false;

        var equalsFunc = _equalsCache.GetOrAdd(type, CreateEqualsExpression);
        return equalsFunc(obj1, obj2);
    }

    public static int GetHashCodeByValue(object obj)
    {
        if (obj == null) return 0;
        var type = obj.GetType();
        var hashCodeFunc = _hashCodeCache.GetOrAdd(type, CreateHashCodeExpression);
        return hashCodeFunc(obj);
    }

    private static Func<object?, object?, bool> CreateEqualsExpression(Type type)
    {
        var param1 = Expression.Parameter(typeof(object), "obj1");
        var param2 = Expression.Parameter(typeof(object), "obj2");
        var cast1 = Expression.Convert(param1, type);
        var cast2 = Expression.Convert(param2, type);

        var conditions = new List<Expression>();

        // 添加字段比较逻辑
        foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
        {
            var fieldAccess1 = Expression.Field(cast1, field);
            var fieldAccess2 = Expression.Field(cast2, field);
            var equals = Expression.Call(typeof(object), nameof(object.Equals), null, fieldAccess1, fieldAccess2);
            conditions.Add(equals);
        }

        // 添加属性比较逻辑(跳过索引器)
        foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
        {
            if (prop.GetIndexParameters().Length > 0) continue;
            var propAccess1 = Expression.Property(cast1, prop);
            var propAccess2 = Expression.Property(cast2, prop);
            var equals = Expression.Call(typeof(object), nameof(object.Equals), null, propAccess1, propAccess2);
            conditions.Add(equals);
        }

        var body = conditions.Aggregate(Expression.AndAlso);
        var lambda = Expression.Lambda<Func<object?, object?, bool>>(body, param1, param2);
        return lambda.Compile();
    }

    private static Func<object, int> CreateHashCodeExpression(Type type)
    {
        var param = Expression.Parameter(typeof(object), "obj");
        var cast = Expression.Convert(param, type);

        var hashVar = Expression.Variable(typeof(int), "hash");
        var initHash = Expression.Assign(hashVar, Expression.Constant(17));

        var statements = new List<Expression> { initHash };

        // 处理字段哈希
        foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
        {
            var fieldAccess = Expression.Field(cast, field);
            var valueHash = Expression.Condition(
                Expression.Equal(fieldAccess, Expression.Constant(null)),
                Expression.Constant(0),
                Expression.Call(fieldAccess, nameof(object.GetHashCode), null));
            var updateHash = Expression.Assign(hashVar, Expression.Add(Expression.Multiply(hashVar, Expression.Constant(23)), valueHash));
            statements.Add(updateHash);
        }

        // 处理属性哈希(跳过索引器)
        foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
        {
            if (prop.GetIndexParameters().Length > 0) continue;
            var propAccess = Expression.Property(cast, prop);
            var valueHash = Expression.Condition(
                Expression.Equal(propAccess, Expression.Constant(null)),
                Expression.Constant(0),
                Expression.Call(propAccess, nameof(object.GetHashCode), null));
            var updateHash = Expression.Assign(hashVar, Expression.Add(Expression.Multiply(hashVar, Expression.Constant(23)), valueHash));
            statements.Add(updateHash);
        }

        statements.Add(hashVar);
        var body = Expression.Block(new[] { hashVar }, statements);
        var lambda = Expression.Lambda<Func<object, int>>(body, param);
        return lambda.Compile();
    }
}

使用方式与方案1完全一致,仅需将ValueComparer替换为CompiledValueComparer即可,第一次调用后会缓存编译后的委托,后续性能接近手动实现的Equals/GetHashCode。

使用说明

  • 默认场景下,==或object.Equals仍保持引用比较逻辑,符合你的需求;
  • 仅在需要值比较的场景,显式调用EqualsByValue方法;
  • 哈希码生成同理,在需要基于属性值生成哈希的位置调用GetHashCodeByValue。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 21:04:53