在面向对象编程(OOP)中,四大核心概念——抽象、封装、继承和多态——指导着我们如何组织代码和复用功能。除了语言特性,OOP更是一种设计理念:通过对象组合来构建灵活、可维护的系统。
然而,在实际开发中,直接依赖继承来复用功能往往会带来较高的耦合度和维护成本。GoF 设计模式提出的 “组合优于继承(Composition over Inheritance)” 原则指出,通过组合已有对象而非继承基类来复用功能,通常更灵活、更安全,也更易于扩展。

在 C# 开发中,这一思想同样适用。TypeAdoption 库正是基于组合优先的理念,为开发者提供了一种简洁、高效的方式实现 接口委托 与 组合模式。借助 Roslyn 源生成器,TypeAdoption 在编译阶段自动生成委托代码,避免重复样板,同时保证类型安全,使功能复用既灵活又整洁。
更多阅读:设计模式超简单解读(6):组合模式
什么是 TypeAdoption?
TypeAdoption 是一个 C# 源生成器库,它的核心功能是:
- 通过在类的字段或属性上添加
[Adopt] 特性,自动生成对应接口的实现;
- 将接口方法委托给被标记的内部成员;
- 支持显式接口实现和隐藏接口,使内部成员既能复用接口,又能保持封装性。
简单来说,它帮你自动把内部对象的能力搬到外层类上,让类直接实现接口,而无需手动写大量委托方法。
核心概念
使用 TypeAdoption 之前,需要了解几个关键概念:
- 源生成器:利用 Roslyn 编译器服务,在编译时生成额外的 C# 代码。
- 委托模式:对象将其职责委托给另一个对象来执行。
- 组合优于继承:通过组合其他对象获取功能,而不是通过继承基类。
基本用法
以日志包装器为例,假设我们有一个内部 ILogger<T> 对象,我们希望 ConfiguredLogger<T> 也实现 ILogger<T>,并将方法委托给内部对象:
using TypeAdoption;
public partial class ConfiguredLogger<T>(ILogger<T> innerLogger)
{
[Adopt]
private readonly ILogger<T> _innerLogger = innerLogger;
public bool IsEnabled(LogLevel logLevel)
{
return logLevel > LogLevel.Debug;
}
// 其他 ILogger 成员自动委托给 _innerLogger
}
自动生成的委托代码
TypeAdoption 会在编译时生成如下代码:
public partial class ConfiguredLogger<T> : ILogger<T>
{
public IDisposable? BeginScope<TState>(TState state) where TState : notnull
{
return _innerLogger.BeginScope<TState>(state);
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception? exception, Func<TState, Exception?, string> formatter)
{
_innerLogger.Log(logLevel, eventId, state, exception, formatter);
}
}
无需手动实现接口方法,所有未覆盖的方法都会自动委托给 _innerLogger。
高级用法
当一个类实现多个接口,且接口方法签名可能冲突时,可以使用 显式接口实现,通过 [Adopt(Publicly = false)]:
public partial class ConfiguredLogger<T>(ILogger<T> innerLogger, IConfiguration configuration)
{
[Adopt]
private readonly ILogger<T> _innerLogger = innerLogger;
[Adopt(Publicly = false)] // 生成显式接口实现
private readonly IConfiguration _innerConfiguration = configuration;
public bool IsEnabled(LogLevel logLevel)
{
return logLevel >= _innerConfiguration.GetMinLevel();
}
}
生成的代码示例:
public partial class ConfiguredLogger<T> : ILoggerConfiguration
{
LogLevel ILoggerConfiguration.GetMinLevel() => _innerConfiguration.GetMinLevel();
string? ILoggerConfiguration.LogTag
{
get => _innerConfiguration.LogTag;
set => _innerConfiguration.LogTag = value;
}
int ILoggerConfiguration.MinLevel
{
get => _innerConfiguration.MinLevel;
set => _innerConfiguration.MinLevel = value;
}
}
通过显式接口实现,可以避免接口方法冲突,同时保持内部成员封装性。
支持的成员类型
TypeAdoption 不仅支持方法委托,还支持:
- 属性(Properties)
- 事件(Events)
- 泛型方法(Generic Methods)
因此,它可以广泛应用于各种接口和模式的实现场景。
工作原理
TypeAdoption 的工作流程大致如下:
- 源代码分析:扫描项目中带有
[Adopt] 特性的字段或属性。
- 语义分析:获取标记成员的类型信息以及接口定义。
- 代码生成:为每个需要委托的接口成员生成实现代码。
- 编译集成:生成的代码与原始类合并,形成完整的类定义。
核心源生成器逻辑通常在 AdoptionGenerator.cs 中实现,通过 Roslyn 的 IIncrementalGenerator 接口注册生成逻辑。
应用场景
1、日志包装器
可以在现有日志系统外层添加过滤、格式化或其他业务逻辑,而不改变原有接口实现。
2、数据访问层抽象
创建装饰器(Decorator)模式,实现缓存或审计逻辑:
public partial class CachedRepository<T>(IRepository<T> repository)
{
[Adopt]
private readonly IRepository<T> _repository = repository;
public T GetById(int id)
{
// 缓存逻辑
return _repository.GetById(id);
}
}
3、策略模式实现
可以动态切换不同的行为实现,例如不同算法或处理逻辑。
性能优势
与运行时反射或动态代理相比,TypeAdoption 具有以下优势:
- 编译时生成:避免运行时反射开销。
- 直接调用:生成的方法直接调用内部成员,性能接近手写委托。
- 类型安全:编译时即可检查接口契约,减少潜在错误。
最佳实践
- 使用
partial 类:TypeAdoption 依赖部分类机制生成代码。
- 注意接口冲突:多个接口签名相同的情况,使用
Publicly = false。
- 保持简单:避免过度委托链,以免影响可读性。
与传统方案对比
| 方案 |
优点 |
缺点 |
| 手动实现接口委托 |
可完全控制实现 |
代码重复,维护成本高 |
| 运行时代理 / 反射 |
灵活,运行时可替换对象 |
性能低,缺少编译时检查 |
| TypeAdoption |
编译时生成,性能好,减少重复代码,类型安全 |
编译依赖增加,运行时灵活性较低 |
总结
TypeAdoption 为 C# 开发者提供了一种优雅的方式,实现接口委托与组合模式。
- 自动生成接口实现,减少样板代码
- 支持显式接口,实现封装和冲突解决
- 性能接近手写委托,同时提供编译时类型安全
通过合理使用 TypeAdoption,我们可以构建 灵活、可维护、可扩展的系统,遵循 SOLID 原则中的单一职责原则和开闭原则。这种方法与良好的设计模式实践相结合,能有效提升代码质量。对这类技术实践感兴趣的开发者,欢迎在 云栈社区 交流讨论。
源码下载链接
[1] https://pan.baidu.com/s/1ymidTbyOivXca7htBGAC1w?pwd=vhm3