FileSystemWatcher

ディレクトリやファイルの変更を検出できます。

コンストラクタ

public FileSystemWatcher (
    string path,  // 監視するディレクトリ
    string filter // 見張るファイルの種類
    );
FileSystemWatcher(String, String) - FileSystemWatcher Constructor (System.IO) | Microsoft Learn

pathは、標準またはUNC表記で指定します。

監視 (monitor) の対象とするとロックされるため、pathより上位のディレクトリは削除や名前の変更をできなくなります。しかしpathに対してはできます。

変更を検出できるのはpath内のファイルやディレクトリであって、path自体への変更は検出できません。pathを削除されても検出できませんが、名前を変更された後もpath内への変更は検出できます。

Filterプロパティに空文字列を設定すると"*.*"に変換されて設定されますが、引数のfilterにそれを設定するとそのまま空文字列に設定され、すべてのファイルが監視から除外されます。(.NET Framework 4.6で確認)

プロパティ

プロパティ 内容 既定値
string Filter 監視するファイルを決定するために使用するフィルター文字列 "*.*"
NotifyFilters NotifyFilter ウォッチ (watch) する変更の種類 LastWrite | FileName | DirectoryName
bool EnableRaisingEvents コンポーネントが有効かどうか false
bool IncludeSubdirectories サブディレクトリを監視するかどうか false
ISynchronizeInvoke SynchronizingObject ディレクトリ変更の結果として発行されるイベント ハンドラー呼び出しをマーシャリングするために使用するオブジェクト null
int InternalBufferSize 内部バッファーのサイズ 8192
string Path ウォッチするディレクトリのパス ""
       

Filter

ディレクトリ内で監視するファイルを指定できます。これには「*」と「?」のワイルドカードを指定できますが、"*.txt|*.doc"のように複数のフィルタは指定できません。Remarks - FileSystemWatcher.Filter Property (System.IO) | Microsoft Learn

これはディレクトリに対しても適用されるため、たとえば*.txtと設定していると、名前が.txtで終わるディレクトリしか監視の対象になりません。

NotifyFilter

監視する変更の種類を指定できます。これはNotifyFilters列挙型の組み合わせで指定します。

NotifyFilters列挙型
列挙子 数値 説明
FileName 1 ファイルの名前

ファイル自体の名前だけではなく、作成やコピーによるファイルの名前の変更も対象となる

DirectoryName 2 ディレクトリの名前

ディレクトリ自体の名前だけではなく、パスの一部の変更も対象となる

Attributes 4 The attributes of the file or folder.
Size 8 The size of the file or folder.
LastWrite 16 The date the file or folder last had anything written to it.
LastAccess 32 The date the file or folder was last opened.
CreationTime 64 The time the file or folder was created.
Security 256 The security settings of the file or folder.
Fields - NotifyFilters Enum (System.IO) | Microsoft Learn

すべての値を指定するには、(NotifyFilters)0x17fとすると簡単です。

EnableRaisingEvents

Pathプロパティに監視するパスを指定し、このEnableRaisingEventsをtrueとすることで監視が開始されます。これはEnableRaisingEventsをfalseにするか、Dispose()によりオブジェクトが破棄されると停止します。

なおWaitForChanged()を呼び出すと、このプロパティがfalseであってもイベントが発生します。

SynchronizingObject

Changedなどのイベントは、既定ではスレッドプールから呼ばれます。それを、このプロパティにControlクラスなどのISynchronizeInvokeを実装したクラスを設定することで、そのクラスと同一のスレッドから呼ばれるように変更できます。

このプロパティがnullのとき、ハンドラ内で投げられた例外を捕捉しないと通知なくアプリケーションが終了します。

InternalBufferSize

内部バッファのバイト数を表します。既定は8192バイトで各イベントは最大16バイトのメモリーを使用するため、512ほどのイベントが同時に発生するとオーバーフローする恐れがあります。かといってバッファのサイズを増加させるのは高価なため、NotifyFilterとIncludeSubdirectoriesで対象を制限し、イベントの発生を抑制します。

イベント

イベント 発生タイミング
FileSystemEventHandler Changed ファイルまたはディレクトリの、サイズ、属性、更新日時、アクセス日時またはパーミッションが変更されたとき
FileSystemEventHandler Created ファイルまたはディレクトリが作成されたとき。監視下のディレクトリへファイルがコピーや移動されたときも含む
FileSystemEventHandler Deleted ファイルまたはディレクトリが削除されたとき。監視下のディレクトリからファイルが移動されたときも含む
RenamedEventHandler Renamed ファイルまたはディレクトリの名前が変更されたとき。監視しているディレクトの名前が変更されたときを除く
ErrorEventHandler Error  
events - FileSystemWatcher クラス (System.IO) | Microsoft Learn
string path = @"C:\path"; // 監視するディレクトリ
string filter = "*.txt";  // 監視するファイルの種類

FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(path, filter);
fileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite;
fileSystemWatcher.IncludeSubdirectories = true;

FileSystemEventHandler FileSystemEvent = delegate (object sender, FileSystemEventArgs e)
{
    // FileInfo file = new FileInfo(e.FullPath);
    Console.WriteLine($"{e.ChangeType} : {e.FullPath}");
};

RenamedEventHandler RenamedEvent = delegate (object sender, RenamedEventArgs e)
{
    Console.WriteLine($"{e.ChangeType} : {e.OldFullPath} -> {e.FullPath}");
};

ErrorEventHandler ErrorEvent = delegate (object sender, ErrorEventArgs e)
{
    Exception exception = e.GetException();
    Console.WriteLine($"{exception.HResult} {exception.Message}");
};

fileSystemWatcher.Changed += FileSystemEvent;
fileSystemWatcher.Created += FileSystemEvent;
fileSystemWatcher.Deleted += FileSystemEvent;
fileSystemWatcher.Renamed += RenamedEvent;
fileSystemWatcher.Error += ErrorEvent;

fileSystemWatcher.EnableRaisingEvents = true;

ファイルシステムやアプリケーションによっては、複数のイベントが発生することがあります。Remarks - FileSystemWatcher.Changed Event (System.IO) | Microsoft Learn

エクスプローラで ファイルの新規作成 (NTFS)
Created : C:\1.txt

同上 (FAT32、exFAT)
Created : C:\1.txt
Changed : C:\1.txt


エクスプローラで 名前の変更 (NTFS、FAT32、exFAT共通)
Renamed : C:\1.txt -> C:\2.txt

エクスプローラで 大文字/小文字が異なるだけの名前の変更 (NTFS)
※FAT32やexFATではこのような変更はできない
Deleted : C:\a.txt
Renamed : C:\a.txt -> C:\A.txt

エクスプローラで ファイルの削除 (共通)
Deleted : C:\2.txt


エクスプローラで 0バイトのファイルのコピー (共通)
Created : C:\1 - コピー.txt
Changed : C:\1 - コピー.txt

エクスプローラで 1バイト以上のファイルのコピー (NTFS)
Created : C:\3 - コピー.txt
Changed : C:\3 - コピー.txt
Changed : C:\3 - コピー.txt


エクスプローラで 同名のファイルに上書きして ファイルのコピー (NTFS)
Changed : C:\1.txt
Changed : C:\1.txt
Changed : C:\1.txt

同上 (FAT32、exFAT)
Changed : C:\1.txt
Changed : C:\1.txt

ディレクトリ内でファイルを操作すると、NTFSではそのディレクトリのChangedイベントも発生します。

エクスプローラで 名前の変更 (NTFS)
Renamed : C:\DIR\1.txt -> C:\DIR\2.txt
Changed : C:\DIR

同上 (FAT32、exFAT)
Renamed : C:\DIR\1.txt -> C:\DIR\2.txt

move、copy、delコマンドで、名前の変更、削除、コピー、移動をした場合も、エクスプローラと同様にイベントが発生します。

移動

エクスプローラで 監視外のディレクトリへファイルを移動
Deleted : C:\1.txt

エクスプローラで 監視外から監視下のディレクトリへファイルを移動
Created : C:\1.txt


エクスプローラで 監視下のディレクトリ内でファイルを移動 (NTFS)
Deleted : C:\SRC\1.txt (移動元)
Created : C:\DEST\1.txt (移動先) (ファイルの更新日時は 移動前の日時のまま)
Changed : C:\DEST
Changed : C:\SRC

同上 (FAT32、exFAT)
Deleted : C:\SRC\1.txt (移動元)
Created : C:\DEST\1.txt (移動先) (ファイルの更新日時は 移動前の日時のまま)


エクスプローラで 監視下の2つのドライブ間でファイルを移動 (NTFS)
Created : D:\1.txt (移動先) (ファイルの更新日時は 現在の日時)
Changed : D:\1.txt
Changed : D:\1.txt          (ファイルの更新日時が 移動前の日時になる)
Deleted : C:\1.txt (移動元)

同上 (FAT32、exFAT)
Created : D:\1.txt (移動先) (ファイルの更新日時は 現在の日時)
Changed : D:\1.txt          (ファイルの更新日時が 移動前の日時になる)
Deleted : C:\1.txt (移動元)

移動はDeletedとCreatedで通知されるため、これが移動によるものであると断定するには、2つのファイルが同一のものであることを確認しなければなりません。c# - Detecting moved files using FileSystemWatcher - Stack Overflow

上書きして移動

エクスプローラで 監視下のディレクトリ内で 同名のファイルに上書きして ファイルを移動 (NTFS)
Deleted : C:\DEST\1.txt (移動先)
Deleted : C:\SRC\1.txt (移動元)
Created : C:\DEST\1.txt
Changed : C:\DEST
Changed : C:\SRC

同上 (FAT32、exFAT)
Deleted : C:\SRC\1.txt (移動元)
Changed : C:\DEST\1.txt (移動先)


エクスプローラで 監視下の2つのドライブ間で 同名のファイルに上書きして ファイルを移動 (NTFS)
Changed : C:\1.txt (移動先)
Changed : C:\1.txt
Changed : C:\1.txt
Deleted : D:\1.txt (移動元)

同上 (FAT32、exFAT)
Changed : C:\1.txt (移動先)
Changed : C:\1.txt
Deleted : D:\1.txt (移動元)
ディレクトリ
// ファイル 1.txtを含むディレクトリ DIRをごみ箱に移動 (NTFS)
Deleted : C:\DIR

// ファイル 1.txtを含むディレクトリ DIRを完全に削除 (NTFS)
Deleted : C:\DIR\1.txt
Deleted : C:\DIR
ディレクトリのコピー
// 2つのファイル 1.txt、2.txtを含むディレクトリ DIRをコピー (NTFS)
Created : C:\DIR - コピー
Changed : C:\DIR - コピー
Created : C:\DIR - コピー\1.txt
Changed : C:\DIR - コピー\1.txt
Changed : C:\DIR - コピー\1.txt
Created : C:\DIR - コピー\2.txt
Changed : C:\DIR - コピー\2.txt
Changed : C:\DIR - コピー\2.txt

同上 (FAT32、exFAT)
Created : C:\DIR - コピー
Changed : C:\DIR - コピー
Created : C:\DIR - コピー\1.txt
Changed : C:\DIR - コピー\1.txt
Created : C:\DIR - コピー\2.txt
Changed : C:\DIR - コピー\2.txt
ディレクトリの移動
// 2つのファイル 1.txt、2.txtを含むディレクトリ DIRを移動 (NTFS)
Deleted : C:\SRC\DIR (移動元)
Created : C:\DEST\DIR (移動先)
Changed : C:\DEST
Changed : C:\SRC

同上 (FAT32、exFAT)
Deleted : C:\SRC\DIR (移動元)
Created : C:\DEST\DIR (移動先)


// 2つのファイル 1.txt、2.txtを含むディレクトリ DIRを監視下の2つのドライブ間で移動 (NTFS)
Created : D:\DIR (移動先)
Changed : D:\DIR
Created : D:\DIR\1.txt
Changed : D:\DIR\1.txt
Changed : D:\DIR\1.txt
Deleted : C:\DIR\1.txt (移動元)
Changed : D:\DIR
Created : D:\DIR\2.txt
Changed : D:\DIR\2.txt
Changed : D:\DIR\2.txt
Deleted : C:\DIR\2.txt
Changed : D:\DIR
Deleted : C:\DIR

同上 (FAT32、exFAT)
Created : D:\DIR (移動先)
Changed : D:\DIR
Created : D:\DIR\1.txt
Changed : D:\DIR\1.txt
Deleted : C:\DIR\1.txt (移動元)
Created : D:\DIR\2.txt
Changed : D:\DIR\2.txt
Deleted : C:\DIR\2.txt
Deleted : C:\DIR
階層化されたディレクトリの移動

DIR_A\DIR_B\1.txtのように配置された、上位のディレクトリを移動する場合を考えます。

// ディレクトリ DIR_Aを移動 (NTFS)
Deleted : C:\SRC\DIR_A (移動元)
Created : C:\DEST\DIR_A (移動先)
Changed : C:\DEST
Changed : C:\SRC

// ディレクトリ DIR_Aを監視下の2つのドライブ間で移動 (NTFS)
Created : D:\DIR_A (移動先)
Changed : D:\DIR_A
Created : D:\DIR_A\DIR_B
Changed : D:\DIR_A\DIR_B
Changed : D:\DIR_A
Created : D:\DIR_A\DIR_B\1.txt
Changed : D:\DIR_A\DIR_B\1.txt
Changed : D:\DIR_A\DIR_B\1.txt
Deleted : C:\DIR_A\DIR_B\1.txt (移動元)
Changed : D:\DIR_A\DIR_B
Deleted : C:\DIR_A\DIR_B
Deleted : C:\DIR_A

Changed

ハンドラに渡されるFileSystemEventArgs.Nameプロパティの値は、監視しているディレクトリからの相対パスです。注釈 - FileSystemEventArgs.Name プロパティ (System.IO) | Microsoft Learn

ファイルを処理する方法によっては、Changedイベントは繰り返し発生することがあります。Remarks - FileSystemWatcher.Changed Event (System.IO) | Microsoft Learn

メモ帳で 名前を付けて保存
Created : C:\Save.txt (ファイルの更新日時は 1601/01/01 9:00:00)
Deleted : C:\Save.txt
Created : C:\Save.txt (ファイルの更新日時は 現在の日時)
Changed : C:\Save.txt

メモ帳で 上書き保存
Changed : C:\Save.txt

Notepad++で 上書き保存
Changed : C:\Save.txt
Changed : C:\Save.txt

c# - FileSystemWatcher Changed event is raised twice - Stack Overflow

イベントを発生させたのがファイルかディレクトリのいずれであるかをパスから判定するのは困難のため、FileSystemWatcherを2つ用意し、それぞれのNotifyFilterにFileNameとDirectoryNameを設定し別々に監視します。c# - .NET filesystemwatcher - was it a file or a directory? - Stack Overflow

Error

内部バッファのオーバーフローなどにより、変更を監視できなくなったときに発生します。FileSystemWatcher.Error Event (System.IO) | Microsoft Learn

エラーの原因は、ハンドラのErrorEventArgs.GetException()からExceptionを取得することで判別でき、下表のような内容です。

HResult Message
0x80004005 "ファンクションが間違っています。" (Incorrect function.)
"ネットワーク名が見つかりません。"
0x80131905 "ディレクトリ *** で、一度に加えられた変更が多すぎます。"

このイベントは監視できなくなったときに必ず発生するわけではなく、たとえばドライブが取り外されてもそれを検出できません。

FileSystemEventArgsクラス

FileSystemWatcherのイベントのデータが提供されます。

このクラスのコンストラクタは次のように定義されており、監視するディレクトリの絶対パスと検出されたファイルの相対パスを渡します。FileSystemEventArgs(WatcherChangeTypes, String, String) コンストラクター (System.IO) | Microsoft Learn

public FileSystemEventArgs(WatcherChangeTypes changeType, string directory, string name)
{
    this.changeType = changeType;
    this.name = name;

    // Ensure that the directory name ends with a "\"
    if (!directory.EndsWith("\\", StringComparison.Ordinal)) {
        directory = directory + "\\";
    }

    this.fullPath = directory + name;
}
FileSystemEventArgs - FileSystemEventArgs.cs
プロパティ
プロパティ 内容
string FullPath 影響を受けるファイルまたはディレクトリの絶対パス

(監視するディレクトリ + Name の文字列。監視するディレクトリのパスは、イベントの発生元であるFileSystemWatcherのPathプロパティから得られる)

string Name 影響を受けるファイルまたはディレクトリの名前

(監視するディレクトリからの相対パスであり、ファイル名やディレクトリ名ではない)

WatcherChangeTypes ChangeType 発生したディレクトリ イベントの種類

RenamedEventArgsクラス

FileSystemEventArgsクラスを継承しており、それに「影響を受けるファイルまたはディレクトリの以前の名前」であるOldNameプロパティが追加されています。

public RenamedEventArgs(WatcherChangeTypes changeType, string directory, string name, string oldName)
    : base(changeType, directory, name) {

    // Ensure that the directory name ends with a "\"
    if (!directory.EndsWith("\\", StringComparison.Ordinal)) {
        directory = directory + "\\";
    }

    this.oldName = oldName;
    this.oldFullPath = directory + oldName;
}
RenamedEventArgs - RenamedEventArgs.cs
Microsoft Learnから検索