filesystemwatcher仅通知文件变更而不保存历史版本,需自行实现快照机制;必须显式启用事件、监听created/changed/renamed三类事件,快照须存于隔离目录并记录准确时间戳。

文件变更监听用 FileSystemWatcher 不等于能做时间旅行
它只通知“变了”,不记录“变成什么样”。想回溯内容,得自己存快照。常见错误是以为启动 FileSystemWatcher 就自动有了历史版本——其实它连上一次的内容都不保存。
典型使用场景:调试配置文件热更新逻辑、排查第三方工具悄悄改写 JSON/YAML 的行为、验证自己写的写入逻辑是否被覆盖。
-
FileSystemWatcher的EnableRaisingEvents = true必须显式设置,否则静默失效 - 事件(如
Changed)可能被合并触发,同一秒内多次写入可能只收到一次通知,不能靠它计数或断言修改次数 - 不要在事件回调里直接读文件——可能遇到“文件正被另一进程占用”异常,需加重试或用
File.OpenRead配合try/catch
每次变更后保存快照要避开锁和竞态
最简方案是把当前文件复制成带时间戳的副本,但直接用 File.Copy 在监听回调里调容易崩。Windows 下编辑器(如 VS Code)保存时先写临时文件再原子替换,FileSystemWatcher 会先报 Created 再报 Renamed,如果只监听 Changed 就漏掉了。
- 必须同时订阅
Created、Changed、Renamed三个事件,尤其关注RenameEventArgs.OldFullPath和Fullpath - 快照路径建议用
Path.Combine(snapshotDir, $"{Path.GetFileName(file)}_{DateTime.UtcNow:yyyyMMddHHmmssfff}.bak",避免中文或特殊字符导致后续解析失败 - 用
File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)读取,允许其他进程同时写
回溯时按时间戳还原比按文件名排序更可靠
手动用 Directory.GetFiles 拿一堆 .bak 文件,然后 OrderBy(x => x)?错。文件名里的时间戳若没补零(比如 12:1:5 vs 12:01:05),字符串排序会乱序。真实回溯依赖的是写入时间(File.GetCreationTimeUtc),不是文件名。
- 保存快照后,立刻用
File.SetCreationTimeUtc(snapshotPath, DateTime.UtcNow)对齐时间戳,后续统一按这个查 - 查询“5 分钟前的版本”别用
DateTime.Now.AddMinutes(-5),用DateTime.UtcNow避免本地时区干扰 - 如果目标文件被删了,快照目录里找不到匹配项,就该触发告警而不是静默返回 null
调试时别让快照本身触发新一轮监听
把快照写进被监听的同一目录?恭喜,FileSystemWatcher 立刻递归触发新事件,可能无限循环。更糟的是,某些编辑器(如 Notepad++)保存时会先删原文件再新建,Deleted 事件后你去存快照,结果又触发一次 Created。
- 快照目录必须和监听目录物理隔离,比如监听
C:\app\config\,快照存到C:\app\snapshots\ - 给
FileSystemWatcher设置IncludeSubdirectories = false,除非你明确需要递归,否则子目录增加事件数量和误触概率 - 在事件回调开头加
if (e.FullPath.EndsWith(".bak", StringComparison.OrdinalIgnoreCase)) return;是懒办法,但比没强
真正难的不是监听或保存,是厘清“谁在什么时候以什么方式改了哪一行”。快照只解决“是什么”,要回答“为什么”,还得结合日志上下文——比如在写快照前,记下当前线程 ID、调用栈(Environment.StackTrace)和 Process.GetCurrentProcess().ProcessName。










