文件和文件夾不存在的時候,FileSystemWatcher 監聽不到文件的改變?如果遞歸地監聽就可以了


 

當你需要監視文件或文件夾的改變的時候,使用 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)
{
    // 當文件改變的時候,這里的代碼會執行。
}

以上代碼的含義是:

  1. 將文件路徑取出來,分為文件夾部分和文件部分;
  2. 判斷文件夾是否存在,如果不存在,則創建文件夾;
  3. 監視文件夾中此文件的改變。

需要說明的是,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:\a\b\x.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:\Users\walterlv\Desktop\demo.txt");
_watcher.Changed += OnFileChanged;
_watcher.Watch();
private void OnFileChanged(object sender, EventArgs e)
{
    // 最純粹的文件改變事件,僅在文件的內容真的改變的時候觸發。
}

此方法的特點,優勢和不足

實際上,FileSystemWatcher 的監視也是有一些空洞的。如果你只是監視一級文件夾而不是遞歸監視子文件夾(通過設置 IncludeSubdirectories 屬性來指定),那么就會存在一些情況是監視不到的。然而如果你真的遞歸監視子文件夾,又會監聽到大量的事件需要過濾。

那么此方法可以支持和不支持的情況有哪些呢?

依然假設監視的文件是:C:\a\b\x.txt

支持這些情況:

  1. 一開始文件 x.txt 不存在,而后創建。
  2. 一開始 b\x.txt 不存在,而后依次創建。
  3. 從 y.txt 文件重命名到 x.txt。
  4. 一開始文件 x.txt 存在,而后刪除,再然后重新創建。

不支持這些情況:

  1. 一開始文件存在,但你直接刪除了 a 或者 b 文件夾,而不是先刪除了 x.txt。
  2. 一開始文件存在,但直接將 b\x.txt 連文件帶文件夾一起移走,然后刪除文件或文件夾。
  3. 一開始 b\x.txt 都不存在,但現在保持文件夾結構連文件帶文件夾一起移入到 a 文件夾中。

當然,也有一些意外的發現:

  1. 一開始文件存在,但直接將 b\x.txt 連文件帶文件夾一起移走,這時依然能監聽到 x.txt 文件的改變,但它已經不在原來的目錄了。

附所有源碼

如果看不到,請訪問:FileWatcher that helps you to watch a single file change even if the file or it’s owner folers does not exists.


參考資料


我的博客會首發於 https://walterlv.com/,而 CSDN 和博客園僅從其中摘選發布,而且一旦發布了就不再更新。

知識共享許可協議

本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名呂毅(包含鏈接:https://blog.csdn.net/wpwalter),不得用於商業目的,基於本文修改后的作品務必以相同的許可發布。如有任何疑問,請與我聯系


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM