C#中适配器模式默认使用对象适配器,因其通过组合+接口实现,规避了单继承限制与sealed类问题;类适配器因C#不支持多重继承且易引发编译/运行时问题而基本不用。

直接说结论:C#里实现适配器模式,**对象适配器是默认选择,类适配器基本不用**——因为C#不支持多重继承,且被适配类常为 sealed 或无参构造受限,强行用类适配器容易卡死在编译或运行时。
对象适配器怎么写(最常用、最安全)
核心就是「组合 + 接口实现」:适配器类实现目标接口 ITarget,内部持有一个 Adaptee 实例,在方法里转发调用。
- 必须显式传入
Adaptee实例(推荐构造函数注入),避免在适配器里 new 出来——否则生命周期失控、测试难、依赖隐藏 - 如果
Adaptee有IDisposable,适配器通常也要实现IDisposable,并正确调用_adaptee.Dispose() - 不要把未映射的方法留空或只写
{};该抛NotSupportedException就抛,比如BeginScope在日志适配中多数不支持 - 示例片段:
public class OldLogSystemAdapter : ILogger { private readonly OldLogSystem _oldLogSystem; public OldLogSystemAdapter(OldLogSystem oldLogSystem) => _oldLogSystem = oldLogSystem; public void Log(LogMessage message) { _oldLogSystem.Log($"[{message.Level}] {message.Message}"); } public IDisposable BeginScope<TState>(TState state) => throw new NotSupportedException("OldLogSystem does not support scopes"); }
类适配器为什么几乎不用
它要求适配器同时继承 Adaptee 并实现 ITarget,但C#只允许单继承。即使 Adaptee 不是 sealed,也极易踩坑:
-
Adaptee是sealed?直接编译失败 —— 现代.NET库中大量基础类型(如HttpClientHandler、ConfigurationSection)都是sealed -
Adaptee构造函数带参数?子类必须显式调用,而适配逻辑可能根本不需要初始化它的全部状态 - 父类方法行为变更(如重写
ToString()或加新虚方法),会隐式影响所有类适配器,耦合太紧 - 你没法对
Adaptee做 mock 或替换——单元测试基本废掉
什么时候必须用对象适配器
不是“推荐”,而是现实倒逼你只能选它:
- 被适配类来自第三方 NuGet 包(比如
NpgsqlConnection、Azure.Core.Pipeline.HttpPipeline),你无法改源码也无法继承 - 目标接口是
IAsyncEnumerable<T>这类现代异步接口,而旧类只提供同步方法(如ReadAllBytes()),必须靠包装层做转换 - 需要控制适配器的生命周期(比如注册为
Scoped服务),而Adaptee是Singleton—— 组合关系才能解耦生命周期 - 一个
Adaptee要适配多个目标接口(如同时适配ILogger和IEventPublisher),类适配器做不到一拖多
容易被忽略的 Dispose 和线程安全问题
适配器不是胶水代码,它是资源代理者。一旦 Adaptee 持有文件句柄、网络连接或 unmanaged 内存,适配器就必须参与资源管理链:
- 若
Adaptee实现了IDisposable,适配器应实现它,并在Dispose()中调用_adaptee.Dispose()—— 别忘了base.Dispose()链和disposed标志位 - 若
Adaptee是线程不安全的(如老式ArrayList或自定义缓存类),适配器不能假设调用方会加锁;要么内部加锁,要么文档明确写「非线程安全」 - 别在
Finalize里释放托管资源(如List<T>),.NET 6+ 已不鼓励;重点盯住 unmanaged 句柄和SafeHandle子类


















