如何重构仅Equals实现不同的两个近乎相同的结构体?
建议方案:用泛型+策略模式抽象相等性逻辑
你不想用类,又要复用结构体的大部分代码同时区分相等性逻辑,接口是可行的,但更推荐泛型结合策略模式的方案——既避免重复代码,又保持值类型的性能优势,同时清晰分离相等性逻辑。
具体实现步骤
1. 定义相等性策略接口
先抽象出相等性判断和哈希计算的契约,所有不同的相等逻辑都实现这个接口:
public interface IEqualityStrategy<T> { bool Equals(T x, T y); int GetHashCode(T obj); }
2. 编写通用泛型结构体
把两个结构体的公共逻辑全部放到这个泛型结构体里,仅通过泛型参数注入不同的相等性策略:
public readonly struct GenericStruct<TStrategy> : IEquatable<GenericStruct<TStrategy>> where TStrategy : IEqualityStrategy<GenericStruct<TStrategy>>, new() { // 这里放两个结构体共有的字段/属性(比如你的业务字段) public int Id { get; } public string Name { get; } // 静态复用策略实例,避免每次创建 private static readonly TStrategy _equalityStrategy = new TStrategy(); public GenericStruct(int id, string name) { Id = id; Name = name; } // 委托给策略实现相等判断 public bool Equals(GenericStruct<TStrategy> other) { return _equalityStrategy.Equals(this, other); } public override bool Equals(object obj) { return obj is GenericStruct<TStrategy> other && Equals(other); } // 委托给策略计算哈希值 public override int GetHashCode() { return _equalityStrategy.GetHashCode(this); } // 这里放两个结构体共有的其他方法,完全复用 public void DoCommonBusinessLogic() { // 公共业务逻辑实现 } }
3. 实现不同的相等性策略
针对原来的AStruct和BStruct,分别实现对应的策略结构体:
// 对应原AStruct的相等逻辑 public struct AEqualityStrategy : IEqualityStrategy<GenericStruct<AEqualityStrategy>> { public bool Equals(GenericStruct<AEqualityStrategy> x, GenericStruct<AEqualityStrategy> y) { // 原AStruct的Equals实现逻辑 return x.Id == y.Id && string.Equals(x.Name, y.Name, StringComparison.Ordinal); } public int GetHashCode(GenericStruct<AEqualityStrategy> obj) { // 原AStruct的GetHashCode实现逻辑 return HashCode.Combine(obj.Id, obj.Name); } } // 对应原BStruct的相等逻辑 public struct BEqualityStrategy : IEqualityStrategy<GenericStruct<BEqualityStrategy>> { public bool Equals(GenericStruct<BEqualityStrategy> x, GenericStruct<BEqualityStrategy> y) { // 原BStruct的Equals实现逻辑(比如忽略Name的大小写) return x.Id == y.Id && string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase); } public int GetHashCode(GenericStruct<BEqualityStrategy> obj) { // 原BStruct的GetHashCode实现逻辑(对应忽略大小写的哈希) return HashCode.Combine(obj.Id, obj.Name.ToLowerInvariant()); } }
4. 使用方式
直接通过泛型参数指定策略,得到和原结构体功能一致的类型:
// 等价于原AStruct var aInstance = new GenericStruct<AEqualityStrategy>(1, "Test"); // 等价于原BStruct var bInstance = new GenericStruct<BEqualityStrategy>(1, "test");
为什么不直接用普通接口?
如果直接让两个结构体实现同一个自定义相等接口,会存在两个问题:
- 装箱性能损耗:当你通过接口类型调用Equals时,值类型结构体会被装箱,影响性能;
- 代码重复无法避免:两个结构体的公共字段、方法还是要重复写,达不到复用目的。
而泛型+策略模式的方案,既通过接口抽象了相等性逻辑,又完全复用了结构体的公共代码,同时因为策略是值类型且通过静态字段复用,不会产生装箱开销,完美适配你的需求。
备选方案:源码生成器
如果追求极致性能,不想有任何策略调用的间接开销,可以用Roslyn源码生成器,基于模板自动生成两个结构体,仅替换Equals和GetHashCode的实现部分。这种方式代码零重复,性能和手写结构体完全一致,但需要编写生成器代码,适合需要大量类似结构体的场景。
内容的提问来源于stack exchange,提问作者Atralupus
相关产品推荐
相关产品推荐

