标准访问者模式中Accept方法必须显式实现,因C#重载解析依赖静态类型,Visit(this)才能触发正确Visit重载;IVisitor接口随元素增加而膨胀,维护成本高;ObjectStructure需注意遍历控制、状态安全与集合快照。

标准访问者模式在 C# 中无法绕过 Accept 方法的显式实现——每个具体元素类必须重写 Accept 并调用 visitor.Visit(this),否则双分派失效,访问者收不到正确类型的回调。
为什么 Visit(this) 不能写成 Visit((IElement)this)
因为 C# 的方法重载解析发生在编译期,依赖的是 this 的**静态类型**。如果在 Book.Accept 里写 v.Visit((IElement)this),编译器只会绑定到 Visit(IElement)(如果存在),而不会触发 Visit(Book book)。结果是所有元素都走同一个泛化入口,失去类型特异性。
-
Book.Accept必须写v.Visit(this):此时this静态类型是Book,编译器才能选中Visit(Book book) - 若误转型为基类或接口,就退化为单分派,访问者逻辑无法按真实子类型区分行为
- 没有运行时重载查找机制;
dynamic能绕过,但代价是丢失编译检查和性能
IVisitor 接口膨胀时怎么应对
每新增一个元素类型,就得往 IVisitor 里加一个 Visit(XxxElement) 方法,所有已存在的 ConcreteVisitor 实现都得补上空实现或抛 NotSupportedException——这是该模式最现实的维护痛点。
- 不建议用
params object[]或泛型Visit<T>(T element)替代:前者丢类型,后者仍需手动 dispatch - 可考虑用 Roslyn 源生成器自动生成
IVisitor声明和默认实现,配合约定命名(如所有元素类继承自IElement并标记[Visitable]) - 若元素来自第三方库且不可改,
dynamic是唯一可行路径,但要接受 IDE 不提示、运行时报错、无重构支持
ObjectStructure.Accept 的常见陷阱
ObjectStructure 类看似只是个容器,但它对访问流程有隐性控制权:它决定遍历顺序、是否跳过某些元素、是否支持中断或并行访问。
- 不要在
Accept内部做状态缓存(如_lastVisitor),访问者应是无状态的;否则并发调用会出错 - 若元素集合可能被外部修改,应在
Accept开始前做快照(ToList()),避免Collection was modified异常 - 不推荐在
ObjectStructure里硬编码访问者类型(如if (visitor is ExportVisitor)),这破坏了 Visitor 的开放性
真正难的不是写出第一个 ConcreteVisitor,而是当第 7 个元素类上线、第 4 个访问者需要适配旧版本接口、且其中两个访问者开始共享部分逻辑时,你才发现 Accept 方法里那句看似简单的 v.Visit(this) 其实锁死了整个扩展路径。


















