文件和文件夾不存在的時候,FileSystemWatcher 監聽不到文件的改變?如果遞歸地監聽就可以了
- 2020 年 2 月 10 日
- 筆記
文件和文件夾不存在的時候,FileSystemWatcher 監聽不到文件的改變?如果遞歸地監聽就可以了
2018-12-20 02:05
當你需要監視文件或文件夾的改變的時候,使用 FileSystemWatcher
便可以完成。不過,FileSystemWatcher
對文件夾的監視要求文件夾必須存在,否則會產生錯誤「無效路徑」。
那麼,如果文件或文件夾不存在的時候可以怎麼監視文件的改變呢?更麻煩的是如果頂層很多級文件夾都不存在,怎麼能監視呢?本文將告訴你方法。
本文的代碼適用於 .NET Framework 和 .NET Core,同時不需要任何第三方依賴。
方法一:創建文件夾(在逃避問題,但也不失為一種解決思路)
如果文件夾不存在,把它創建出來就可以監視了嘛!這其實是在逃避問題。不過我把它寫出來是因為如果我不說,可能有些小夥伴原本簡單的問題就會變得複雜化。
public void Watch(string file) { path = Path.GetFullPath(file); var directory = Path.GetDirectoryName(path); var file = Path.GetFileName(path); // 如果文件夾不存在,則創建文件夾。 if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } // 監視 directory 文件夾下的 file 文件的改變 var watcher = new FileSystemWatcher(directory, file) { EnableRaisingEvents = true, NotifyFilter = NotifyFilters.LastWrite, }; watcher.Changed += FinalFile_Changed; // 使用 watcher 做其他的事情。 } private void FinalFile_Changed(object sender, FileSystemEventArgs e) { // 當文件改變的時候,這裡的代碼會執行。 }
以上代碼的含義是:
- 將文件路徑取出來,分為文件夾部分和文件部分;
- 判斷文件夾是否存在,如果不存在,則創建文件夾;
- 監視文件夾中此文件的改變。
需要說明的是,FileSystemWatcher
原本是監視文件夾的,第一個參數是監視的文件夾的路徑,而第二個參數是監視文件或文件夾的過濾通配符。
不過,官方文檔的說明是:
To watch a specific file, set the Filter property to the file name. For example, to watch for changes in the file MyDoc.txt, set the Filter property to 「MyDoc.txt」.
如果你需要監聽一個特定的文件,那麼直接將後面的過濾器設定為文件名,那麼就會直接監視到對應的文件。
如果你的業務當中,反正始終都是要創建這個文件的,那麼一開始創建了這個文件夾就能避免不少的麻煩。這也是我把這個方法放到這裡作為首選方法的原因。雖然實際上這是在逃避問題,但真的是一個好方法。
方法二:遞歸監視文件夾
這種方法適用於如果文件或者文件夾不存在時,你不能創建這個文件夾的情況。也許是你的業務需要,也許因為你正在寫庫,庫作為最為通用的業務,不希望改變用戶的環境。
這時,我們可以考慮的思路是 —— 遞歸地監視文件或文件夾。
例如,我們有這樣的文件夾結構:
C:abx.txt
希望監聽 x.txt 的改變。
那麼,如果 b 文件夾不存在,就監聽 a 文件夾,如果 a 文件夾也不存在,那麼就監聽 C: 驅動器。實際上,我們不需要再去考慮 C: 驅動器也不存在的情況了(當你真的遇到的時候,考慮業務上規避吧……)。
代碼實現
既然需要遞歸監視,那麼我們需要查找第一次監視的時候,需要到哪一層。
這裡,我們可以用一個 while
循環來進行,一層一層查找文件夾。直到能夠找到一層,文件夾存在而子文件夾不存在的情況。這時我們便能夠監視子文件夾的創建了。
我寫了一個函數,用於返回這時存在的那個文件夾,和不存在的那個子文件夾或者文件。
當然有特殊情況,就是文件直接就已經存在的情況下,也是返迴文件所在的文件夾和此文件名的。
private (string directory, string file) FindWatchableLevel() { var path = _file.FullName; // 如果文件存在,就返迴文件所在的文件夾和文件本身。 if (File.Exists(path)) { return (Path.GetDirectoryName(path), Path.GetFileName(path)); } // 如果文件不存在,但文件夾存在,也是返迴文件夾和文件本身。 // 這一點在下面的第一層循環中體現。 // 對於每一層循環。 while (true) { var directory = Path.GetDirectoryName(path); var file = Path.GetFileName(path); // 檢查文件夾是否存在,只要文件夾存在,那麼就可以返回。 if (Directory.Exists(directory)) { return (directory, file); } // 如果連文件夾都不存在,那麼就需要查找上一層文件夾。 path = directory; } }
接下來,根據得到的文件夾和文件,判斷其存在與否,決定是監視這個文件的改變,還是監視文件/文件夾結構的改變。如果文件/文件夾的結構改變,那麼就需要重新調用這個方法再查找應該監視的文件夾了。
public void Watch() { var (directory, file) = FindWatchableLevel(); if (File.Exists(_file.FullName)) { // 如果文件存在,說明這是最終的文件。 // 注意使用 File.Exists 判斷已存在的同名文件夾時會返回 false。 _watcher = new FileSystemWatcher(directory, file) { EnableRaisingEvents = true, NotifyFilter = NotifyFilters.LastWrite, }; _watcher.Changed += FinalFile_Changed; _watcher.Deleted += FileOrDirectory_CreatedOrDeleted; } else { // 注意這裡的 file 可能是文件也可能是文件夾。 _watcher = new FileSystemWatcher(directory, file) { EnableRaisingEvents = true, }; _watcher.Created += FileOrDirectory_CreatedOrDeleted; _watcher.Renamed += FileOrDirectory_CreatedOrDeleted; _watcher.Deleted += FileOrDirectory_CreatedOrDeleted; } }
我們通過 File.Exists(_file.FullName)
來判斷最終的文件是否存在,用以區分是在監視最終的文件改變,還是監視文件夾結構的改變。
private void FileOrDirectory_CreatedOrDeleted(object sender, FileSystemEventArgs e) { // 在文件/文件夾結構發生改變的時候,重新監視。 _watcher?.Dispose(); Watch(); } private void FinalFile_Changed(object sender, FileSystemEventArgs e) { // 這裡就是最終文件改變的地方了。 }
完整的代碼和使用方法
由於代碼還是有一點點多。如果放到你原有的業務當中,對你的業務代碼確實是一種污染。所以我封裝了一個類 FileWatcher
。它不需要依賴任何就可以使用,你可以將它拷貝到你的項目當中。
代碼可以閱讀本文文末,或者前往 gist 查看:FileWatcher that helps you to watch a single file change even if the file or it』s owner folders does not exists.。
使用方法與 FileSystemWatcher
類似,但是更簡單:
_watcher = new FileWatcher(@"C:UserswalterlvDesktopdemo.txt"); _watcher.Changed += OnFileChanged; _watcher.Watch();
private void OnFileChanged(object sender, EventArgs e) { // 最純粹的文件改變事件,僅在文件的內容真的改變的時候觸發。 }
此方法的特點,優勢和不足
實際上,FileSystemWatcher
的監視也是有一些空洞的。如果你只是監視一級文件夾而不是遞歸監視子文件夾(通過設置 IncludeSubdirectories
屬性來指定),那麼就會存在一些情況是監視不到的。然而如果你真的遞歸監視子文件夾,又會監聽到大量的事件需要過濾。
那麼此方法可以支持和不支持的情況有哪些呢?
依然假設監視的文件是:C:abx.txt 。
支持這些情況:
- 一開始文件 x.txt 不存在,而後創建。
- 一開始 bx.txt 不存在,而後依次創建。
- 從 y.txt 文件重命名到 x.txt。
- 一開始文件 x.txt 存在,而後刪除,再然後重新創建。
不支持這些情況:
- 一開始文件存在,但你直接刪除了 a 或者 b 文件夾,而不是先刪除了 x.txt。
- 一開始文件存在,但直接將 bx.txt 連文件帶文件夾一起移走,然後刪除文件或文件夾。
- 一開始 bx.txt 都不存在,但現在保持文件夾結構連文件帶文件夾一起移入到 a 文件夾中。
當然,也有一些意外的發現:
- 一開始文件存在,但直接將 bx.txt 連文件帶文件夾一起移走,這時依然能監聽到 x.txt 文件的改變,但它已經不在原來的目錄了。
附所有源碼
- FileSystemWatcher Class (System.IO) – Microsoft Docs
- c# – How can i use FileSystemWatcher to watch directory if directory not exist? – Stack Overflow
- FileSystemWatcher – Pure Chaos (Part 1 of 2) – CodeProject
- FileSystemWatcher – Pure Chaos (Part 2 of 2) – CodeProject
- Recursive Directory Watch – CodeProject
- How does Read File watch for changes? – Grasshopper
- c# – Reading file after writing it – Stack Overflow
本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發佈,但務必保留文章署名 呂毅 (包含鏈接: https://blog.walterlv.com ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請 與我聯繫 ([email protected]) 。