装饰器模式在c#中通过接口+组合+委托实现,所有组件实现同一接口,装饰器持有该接口实例并委托调用;避免继承,支持运行时动态叠加;需警惕链断裂和无限递归;不同于attribute或dispatchproxy等代理机制,它类型安全、调试友好但依赖显式接口契约。

装饰器模式在 C# 中的核心实现思路
装饰器模式不是靠语言特性(比如 Python 的 @decorator)自动支持的,而是靠接口 + 组合 + 委托来手动构造。关键在于:所有装饰器和被装饰对象必须实现同一接口,装饰器内部持有一个该接口类型的实例,并在方法调用中“包装”它。
典型结构包括:IComponent(统一接口)、ConcreteComponent(原始实现)、Decorator(抽象基类,含 IComponent 字段)、多个具体装饰器(如 LoggingDecorator、RetryDecorator)。
如何用组合代替继承实现动态增强
避免用继承扩展行为——那会提前绑定、无法运行时叠加。装饰器通过构造函数接收 IComponent,把“增强逻辑”写在方法体内,再显式调用 _inner.DoSomething(),从而控制执行时机(比如前置日志、后置清理、异常重试)。
-
LoggingDecorator可在调用前记录开始时间,调用后记录耗时和结果 -
RetryDecorator可捕获异常并循环重试,直到成功或超限 - 多个装饰器可嵌套:new
RetryDecorator(newLoggingDecorator(newHttpService()))
常见错误:装饰器链断裂或无限递归
最容易出问题的是装饰器内部没调用 _inner,或者误调了 this.DoSomething() 导致栈溢出。C# 编译器不会报错,但运行时直接崩溃。
检查点:
- 每个装饰器的构造函数必须接收并保存
IComponent类型参数,不能是具体类型(否则破坏多态) - 所有方法实现中,必须明确调用
_inner.SomeMethod(),而不是this.SomeMethod() - 避免在
ToString()、GetHashCode()等基础方法里意外触发装饰逻辑(除非有意为之)
与 .NET 原生机制(如 Attribute 或 AOP)的区别
C# 的 [Attribute] 本身不执行逻辑,只是元数据;真正拦截调用需要配合 RealProxy(已废弃)、DispatchProxy(.NET Core+)或第三方库(如 Castle DynamicProxy)。这些属于运行时代理,和装饰器模式有本质不同:
- 装饰器是显式构造、类型安全、调试友好,但需手动组合
- 代理是隐式织入、无需改调用方代码,但堆栈深、难调试、部分场景不支持(如 sealed 类、非虚方法)
- 若你只需要日志/授权等横切关注点,
DispatchProxy更轻量;若需精细控制每层行为顺序或状态传递(比如带上下文的缓存装饰器),手写装饰器更可靠
别指望给任意已有类“一键加装饰器”——C# 没有 Ruby 那种开放类机制,所有装饰都建立在接口契约之上。漏掉接口定义,整个模式就跑不起来。









