无需重写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
相关产品推荐
相关产品推荐

