C#中如何共享接口实现代码 解决多接口组合复用问题
单继承体系下通过组合基类复用接口实现会产生N个接口对应2^N个基类的组合爆炸问题,完全不具备可维护性,根据你的项目环境和场景从以下方案中选择即可,所有方案都支持单个接口实现逻辑统一维护、按需给业务类复用:
方案1:C# 8.0及以上优先使用默认接口实现
这是C#原生为横向能力复用设计的语法特性,不需要额外创建实现基类,直接把通用逻辑写在接口定义中,所有实现该接口的类自动复用逻辑,无需重复编写代码。
- 适用场景:接口实现逻辑不需要依赖业务类的独有私有状态,逻辑通用固定(比如示例中
ISelectable的选中逻辑)
示例代码:
public interface ISelectable { bool IsSelected { get; set; } // 通用方法直接在接口内写默认实现,所有实现类直接复用 void Select() => IsSelected = true; void Unselect() => IsSelected = false; } // 业务类仅需声明实现接口,不需要重复编写Select/Unselect逻辑 public class GameCard : ISelectable { // 仅需实现属性,方法自动复用接口默认逻辑 public bool IsSelected { get; set; } // 其他业务逻辑 }
如果某个业务类需要特殊的实现逻辑,直接在类中重写对应方法即可,不会影响其他类使用通用逻辑。
方案2:接口实现需要维护私有状态时,使用扩展方法+弱引用状态表
如果接口实现需要维护私有状态(比如示例中IStorable用到的存储连接、重试配置等私有字段),默认接口无法存储实例级私有状态,可以用扩展方法承载通用逻辑,通过ConditionalWeakTable绑定业务实例和对应的状态对象,完全不需要继承。
- 适用场景:实现逻辑需要维护独立私有状态,且需要支持不同实现变体(比如不同的存储逻辑)
示例代码:
// 定义状态容器,存放接口实现需要用到的私有字段 file class StorableState { public string DbConnectionString { get; set; } public int RetryCount { get; set; } } public static class StorableExtensions { // 线程安全的弱引用表,自动绑定业务实例和对应状态,不会造成内存泄漏 private static readonly ConditionalWeakTable<IStorable, StorableState> _stateStore = new(); // 通用SQL存储实现,所有IStorable实现类可直接复用 public static void StoreToSqlDefault(this IStorable instance) { var state = _stateStore.GetOrCreateValue(instance); // 编写通用存储逻辑,需要的私有数据从state中读取 // storing code } // 可按需扩展其他实现变体,比如Mongo存储、本地文件存储等 public static void StoreToMongoDefault(this IStorable instance) { // 对应存储逻辑 } } // 业务类使用 public class GameResource : IStorable { // 显式实现接口,直接调用通用扩展方法即可 void IStorable.Store() => this.StoreToSqlDefault(); // 其他业务逻辑 }
修改接口实现逻辑时仅需修改扩展方法中的代码,所有接入的业务类自动生效,不需要修改业务类代码。如果某个类需要自定义逻辑,直接在自身的接口实现中编写逻辑即可,不需要走通用扩展方法。
方案3:复杂场景使用源生成器自动注入实现
如果能力接口逻辑复杂、需要访问业务类成员,或者需要减少样板代码,可以实现简单的编译时源生成器:
- 标记需要自动实现的接口,比如给业务类加
[AutoImplement(typeof(IStorable), nameof(StorableExtensions.StoreToSqlDefault))]特性 - 源生成器在编译阶段扫描标记的partial类,自动注入对应接口的实现代码,直接对接你维护的通用逻辑
该方案没有运行时开销,逻辑统一维护,不管多少个接口组合,仅需给类添加对应特性即可,完全不需要编写组合基类。
兼容低版本C#(C# 8.0以下):组合转发
如果项目使用不支持C# 8.0的环境(比如旧版.NET Framework),直接用组合代替继承,不要编写组合基类:每个能力接口对应一个独立的实现类(即你目前编写的Selectable、Storable类),业务类需要什么能力就持有对应实现类的实例,将接口成员的调用转发给持有的实例即可。
示例代码:
public class GameCard : ISelectable, IStorable { // 持有能力实现实例 private readonly Selectable _selectableImpl = new(); private readonly Storable _storableImpl = new(); // 转发调用 public bool IsSelected { get => _selectableImpl.IsSelected; set => _selectableImpl.IsSelected = value; } public void Select() => _selectableImpl.Select(); public void Store() => _storableImpl.Store(); // 其他业务逻辑 }
该方案的缺点是每个业务类需要编写少量转发代码,但不会产生组合基类爆炸的问题,修改接口逻辑时仅需修改对应的独立实现类即可。
不要使用组合基类的方案,当能力接口数量超过3个时,组合基类的数量会呈指数级增长,后续维护完全不可控。以上方案都是将单个接口的实现逻辑独立维护,业务类按需接入对应能力,不需要考虑接口组合问题。
内容的提问来源于stack exchange,提问作者javirs

