模板方法模式在c#中需主入口方法为public sealed、所有钩子方法为protected、禁止构造函数调用模板方法;abstract用于强制实现核心步骤,virtual用于可选扩展点,必须用override重写且不可用new隐藏。

模板方法模式在 C# 中不是靠“写对 abstract/virtual”就完事的,关键在于访问修饰符、密封控制和调用时机这三处——错一处,整个骨架就可能被绕过或失效。
主入口方法必须是 public sealed,不能是 virtual 或 abstract
这是最容易被忽略的安全边界。模板方法(如 Run()、Execute())本质是流程控制器,它的职责是按序调用子步骤,不接受任何重写。
常见错误是把它声明为 virtual public void Run(),结果子类用 override 改了执行顺序,甚至删掉某步;更糟的是用 new 隐藏,父类引用调用时根本走不到子类逻辑。
- 正确做法:直接写成
public void Run() { ... },内部调用virtual或abstract成员 - 如果想显式禁止继承修改,加
sealed:public sealed void Run() - 绝对不要在构造函数里调用
Run()—— 此时子类字段可能还是null
abstract 和 virtual 钩子方法必须是 protected
所有可变步骤(比如 ValidateInput()、SaveResult())对外暴露为 public 是危险的。外部代码可以直接调用它们,跳过前置校验或后置清理,破坏流程完整性。
另一个隐患是子类在构造函数中调用钩子方法——此时对象尚未完成初始化,NullReferenceException 很可能立刻发生。
-
abstract方法用于强制实现的核心差异点,如CalculateDiscount() -
virtual方法用于可选扩展点,如OnBeforeProcess(),默认提供空实现 - 两者都必须声明为
protected,禁止public或internal(除非有明确跨程序集调用需求)
异步场景下不能写 async virtual,得拆成 async Task + virtual Task
C# 编译器不支持 async virtual 语法,直接写会报错。但强行用 .Result 或 .Wait() 在 UI 或 ASP.NET 同步上下文中极易引发死锁。
典型错误是父类方法里写 await hookMethod().Result,或者子类重写时漏掉 await,导致返回未完成的 Task。
- 父类骨架方法:
public async Task ExecuteAsync() { await BeforeStep(); await DoWork(); await AfterStep(); } - 钩子方法:
protected virtual Task BeforeStep() => Task.CompletedTask; - 子类重写:
protected override async Task BeforeStep() { await LoadConfigAsync(); }
子类重写时必须用 override,禁用 new
用 new 关键字隐藏父类方法,看起来能编译通过,但多态失效:父类引用调用时仍执行父类版本,模板流程彻底断裂。
例如 AbstractProcessor p = new ConcreteProcessor(); p.Run(); 中,若 Run() 内部调用了 new 出来的 DoWork(),实际执行的仍是父类空实现。
- 检查点:所有重写方法签名(含返回类型、参数名、参数顺序)必须与父类完全一致
- IDE 通常会提示“‘xxx’ hides inherited member ‘yyy’. Use the new keyword if hiding was intended”,看到这个警告就得删掉
new改用override - 抽象类里对应方法必须已标记
virtual或abstract,否则无法override
最常被轻视的是 protected 修饰符和构造函数调用这两条——它们不报错,但会让模板在运行时悄悄失效,排查起来比编译错误更费时间。











