绝大多数情况下无需手写Adapter类,C#的委托、隐式转换等机制更轻量;仅当桥接第三方库或封装不可修改遗留类时才需正式Adapter。

Adapter 模式在 C# 里到底该不该手写
绝大多数情况下,不需要手动实现完整 Adapter 类——C# 的委托、隐式转换、扩展方法甚至 Func<t></t> 就能绕过接口不兼容问题,比写一堆 Adapter 更轻、更易维护。
只有当你要桥接第三方库(比如老版 IDataReader 和新版 IAsyncEnumerable<t></t>)、或封装不可修改的遗留类(如密封的 LegacyPaymentService)时,才值得上正式 Adapter。
- 手写 Adapter 容易变成“套娃”:A → B → C → D,调用链变长,调试时堆栈难读
- 如果目标类型支持
implicit operator,优先用它;它比 Adapter 更自然,且编译器能自动触发 - 别为了“模式而模式”强行套 Adapter:两个类字段名/行为高度一致?直接映射或 record 构造就够了
怎么写一个最小可行的类适配器(Class Adapter)
类适配器用继承,适合目标类可继承、且你控制得了子类使用场景的情况。注意 C# 不支持多继承,所以只能继承一个类,但可以实现多个接口。
典型场景:老系统有个 LegacyLogger 类,只提供 Write(string) 方法,但新代码依赖 ILogger 接口的 Log(LogLevel, EventId, string)。
public class LegacyLoggerAdapter : LegacyLogger, ILogger
{
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
// 把新参数转成旧格式,比如忽略 level 和 eventId
var msg = formatter(state, exception);
Write(msg); // 调用父类方法
}
<pre class="brush:php;toolbar:false;">// 其他 ILogger 成员可抛 NotSupportedException 或空实现
public IDisposable BeginScope<TState>(TState state) => NullScope.Instance;}
- 必须继承具体类(
LegacyLogger),不能继承接口——否则就不是 Class Adapter 了 - 若
LegacyLogger是 sealed,这条路直接走不通,得切到对象适配器 - 所有未使用的
ILogger方法别留空实现,显式抛NotSupportedException,避免调用方误以为功能可用
对象适配器(Object Adapter)更常用,但要注意生命周期
对象适配器用组合,适用性更广,尤其当你无法修改被适配类、或它本身是 sealed 时。但它引入一层引用,容易引发内存泄漏或 Dispose 错误。
常见错误现象:Adapter.Dispose() 没转发给内部 _legacyService,导致资源没释放;或者 _legacyService 被外部提前释放,Adapter 后续调用直接抛 ObjectDisposedException。
- 适配器类自己是否需要实现
IDisposable?取决于内部对象是否实现了它 - 如果内部对象是 long-lived(比如单例
SqlConnection),Adapter 最好也设计为 singleton 或由 DI 容器管理生命周期 - 不要在 Adapter 构造函数里直接 new 内部对象——这会让测试困难;应通过构造函数注入
- 示例关键行:
private readonly LegacyPaymentService _service;+public PaymentAdapter(LegacyPaymentService service) => _service = service;
为什么泛型 Adapter 很少真有用
看着很优雅:public class Adapter<tadaptee ttarget> where TAdaptee : class where TTarget : class</tadaptee>……但实际几乎没法落地。
因为接口转换不是类型擦除,而是行为重映射。两个接口的方法名、参数顺序、语义(比如 Cancel() 是立即终止还是发取消信号)往往不同,泛型无法自动推导逻辑。
- 编译器不帮你做
MapToRequest()或HandleError()这种业务逻辑 - 一旦加了
where TAdaptee : ICancelable这类约束,反而限制了灵活性——很多老类根本没实现任何接口 - 真要复用逻辑,抽成工具方法(如
ConvertToDto<t>(object source)</t>)比泛型 Adapter 更直接 - 如果你正在写 ORM 映射层,用
AutoMapper或Mapster,它们内部是策略+表达式树,不是靠泛型 Adapter
最常被忽略的一点:Adapter 的边界必须清晰。它不该承担数据校验、重试、日志埋点这些事——那些是 Decorator 或 Middleware 的活。Adapter 只做一件事:让“说英语”的代码能和“说法语”的类聊上两句。聊完就退场,别抢戏。


















