C#中event与change token的区别及适用场景咨询
先给结论:基础通知场景下两者功能确实近似,但IChangeToken是针对通用组件设计的抽象封装,有几个event不具备的核心特性,刚好适配.NET生态里很多通用组件的设计需求:
无感知的生命周期管理,彻底避免内存泄漏
event最常见的坑就是订阅者如果不主动退订,发布者会一直持有订阅者的强引用,只要发布者不销毁,订阅者就永远无法被GC回收,尤其是发布者是单例生命周期的场景(比如全局的IOptionsMonitor、全局的IFileProvider),非常容易出现内存泄漏。
而IChangeToken的订阅直接返回IDisposable对象,你不需要记住发布者实例、也不需要单独记退订的事件名,只要销毁这个返回的disposable就能直接解除订阅。哪怕是在临时作用域里订阅,用using包裹就能自动清理,完全不需要额外写退订逻辑。统一的变更通知抽象,支持任意变更源组合
IChangeToken是个通用的接口标准,不管你底层的变更是来自本地文件系统、远程配置中心推送、数据库字段变更、还是自定义的业务状态变化,都可以用同一套接口对外暴露通知。你还可以直接用官方提供的CompositeChangeToken把多个变更源的token合并成一个,比如要同时监听3个配置文件的变更,只需要对外暴露一个合并后的token,订阅者完全不需要感知背后有多少个变更源。
换成event的话,你需要自己实现多事件的转发逻辑,不同变更源的事件签名也很难统一,没法做通用的组合工具。兼容轮询类变更检测,不依赖原生事件源
很多场景下根本没有原生事件可以用:比如监听FTP服务器的文件变更、监听远程配置中心的配置更新、监听数据库某条记录的变化,这类场景你只能靠轮询判断是否有变更。IChangeToken原生支持这类场景,你只要实现HasChanged和ActiveChangeCallbacks两个属性就行,底层是用事件还是轮询,订阅者完全不需要关心。
用event的话你需要自己封装轮询逻辑、自己触发事件、自己处理生命周期,没有统一的标准实现。默认一次性通知,避免重复触发的冗余处理
IChangeToken默认只会触发一次变更通知,触发完成后就直接失效,如果要继续监听下一次变更,重新获取新的token即可。这个特性非常适配文件加载、配置加载这类场景:比如FileSystemWatcher经常会因为文件保存时的多次IO操作触发多次Changed事件,你用ChangeToken封装后可以很方便的做防抖,只对外触发一次通知,避免重复执行加载逻辑。
场景选择建议
如果你符合以下任意一种场景,优先选择IChangeToken:
- 你要开发可复用的通用组件,需要对外暴露统一的变更通知接口,适配不同的变更源
- 发布者的生命周期比订阅者长,比如单例发布者给 transient/scoped 生命周期的订阅者发通知,避免内存泄漏
- 需要组合多个变更源,或者变更源需要靠轮询实现
- 需要避免同一次变更被重复触发处理
如果只是内部模块的简单通知、发布者和订阅者生命周期完全一致,用event完全没问题,不需要硬套ChangeToken。
内容的提问来源于stack exchange,提问作者pschill

