C#如何在接口内实现继承的接口成员 避免结构体重复编码
定义了继承IEquatable<IStat>与IComparable<IStat>的接口IStat:业务逻辑中各类统计属性实例主要以IStat类型被引用,而非其具体实现类型,且无论具体实现类型是什么,IEquatable和IComparable对应的比较逻辑完全一致。
受项目约束,所有IStat的实现必须是值类型结构体,不能使用引用类型类。当前使用默认接口实现加new关键字定义比较方法时,派生结构体仍然被要求手动实现这两个接口方法,需要找到避免重复编写相同逻辑的方案。
现有代码定义
IStat.cs
public interface IStat : IEquatable<IStat>, IComparable<IStat>{ string GetKey(); string GetValue(); public new bool Equals(IStat stat) { // 已知此处不应该使用new关键字 return stat is not null && GetKey() == stat.GetKey(); } public new int CompareTo(IStat stat) { return stat is null?1:GetKey.CompareTo(stat.GetKey()); } } public interface IStat<TKey, TValue> : IStat { TKey Key{get;} TValue Value{get;} }
ExampleStat.cs
// 仍然要求实现Equals和CompareTo,且ExampleStat必须为结构体 public struct ExampleStat : IStat<string, double> { private string m_Key; private double m_Value; public string Key=>m_Key; public double Value=>m_Value; public ExampleStat(string key, double value) { m_Key = key; m_Value = value; } }
核心疑问
- 是否存在合规方式可以直接在
IStat接口内部正确实现IEquatable和IComparable的方法,让派生结构体无需重复实现? - 如果接口本身无法承载这部分统一实现,有什么替代方案可以避免在每个实现类型中重复编写完全相同的比较逻辑?
C#默认接口实现(DIM)不会被值类型结构体自动识别为自身的接口成员实现:结构体实现接口时,必须显式实现接口契约要求的所有成员,默认接口实现仅在以接口类型为调用入口时才会被调用,既无法满足编译器的实现要求,值类型调用时还会产生装箱开销。代码中加new关键字的写法本质是在接口中重新声明了同名方法,并没有覆盖IEquatable<T>/IComparable<T>的契约要求,因此编译器仍然会要求派生结构体手动实现这两个方法,不存在直接在接口内实现就让结构体自动继承生效的合规写法。
方案1:静态扩展方法+最小显式转发(推荐)
该方案仅需要在每个结构体中写两行固定的转发代码,核心比较逻辑完全统一复用,运行时开销极低:
- 移除
IStat中带new的两个默认方法,将比较逻辑统一抽到静态扩展类中public interface IStat : IEquatable<IStat>, IComparable<IStat>{ string GetKey(); string GetValue(); } public interface IStat<TKey, TValue> : IStat { TKey Key{get;} TValue Value{get;} } public static class StatExtensions { public static bool StatEquals(this IStat left, IStat right) { return right is not null && left.GetKey() == right.GetKey(); } public static int StatCompareTo(this IStat left, IStat right) { return right is null ? 1 : left.GetKey().CompareTo(right.GetKey()); } } - 所有实现
IStat的结构体仅需要写两行固定的显式接口实现做转发,不需要重复编写比较逻辑:public struct ExampleStat : IStat<string, double> { private string m_Key; private double m_Value; public string Key=>m_Key; public double Value=>m_Value; public ExampleStat(string key, double value) { m_Key = key; m_Value = value; } // 以下代码所有IStat结构体完全一致,无需修改 bool IEquatable<IStat>.Equals(IStat other) => this.StatEquals(other); int IComparable<IStat>.CompareTo(IStat other) => this.StatCompareTo(other); // 可选:重写ValueType默认方法避免装箱,逻辑同样复用统一方法 public override bool Equals(object obj) => obj is IStat other && this.StatEquals(other); public override int GetHashCode() => m_Key?.GetHashCode() ?? 0; }
所有以
IStat为类型入口的比较调用会直接走统一逻辑,结构体上的显式实现仅做转发,代码量极小,不存在逻辑重复维护的问题。
方案2:源生成器自动生成样板代码
如果不想在每个结构体中手动编写转发代码,可以编写简单的C#源生成器,在编译阶段自动为所有标记了对应特性的IStat结构体生成Equals、CompareTo、Equals(object)、GetHashCode实现,完全消除手动重复代码。
该方案零运行时开销,完全适配结构体场景,是中大型项目消除同类样板代码的常用方案。
不可行方案说明
- 不要尝试通过抽象类承载统一实现:结构体是值类型无法继承抽象类,不符合必须使用值类型的约束
- 不要强行依赖默认接口实现:既无法通过编译检查,值类型调用接口方法还会产生额外装箱开销,性能表现差
内容的提问来源于stack exchange,提问作者user3915050

