Monitor Directory Changes in C# with FileSystemWatcher
The FileSystemWatcher class in .NET provides a built-in
mechanism to listen to file system change notifications and react in
real time when files or directories are created, modified, deleted, or
renamed. This article explains what FileSystemWatcher is,
how to configure and implement it in C#, and how to handle common edge
cases like duplicate events and buffer overflows.
What Is FileSystemWatcher?
FileSystemWatcher is a component located in the
System.IO namespace. It hooks into the underlying operating
system's file notification system (such as the Win32
ReadDirectoryChangesW API on Windows) to track changes in a
specified directory. Instead of manually polling a folder at regular
intervals, which consumes unnecessary CPU and disk resources,
FileSystemWatcher raises asynchronous events immediately
when a file system change occurs.
Essential Properties and Events
To use FileSystemWatcher effectively, you must configure
its target location, what types of changes to listen for, and which
events to subscribe to.
Core Properties
Path: The absolute or relative path of the directory you want to monitor.Filter: A search string used to match specific files (e.g.,*.txt,data_*.json). To monitor all files, set it to*.*or leave it default.IncludeSubdirectories: A boolean indicating whether child directories inside the target path should also be monitored.NotifyFilter: A bitwise combination ofNotifyFiltersflags specifying what attributes trigger an event (e.g.,FileName,DirectoryName,LastWrite,Size).EnableRaisingEvents: A boolean that starts (true) or stops (false) the component from raising events.
Core Events
Created: Triggered when a new file or directory is created.Changed: Triggered when a file or directory is modified (e.g., content changes, attribute changes).Deleted: Triggered when a file or directory is deleted.Renamed: Triggered when a file or directory changes its name.Error: Triggered when the internal buffer overflows or a system error occurs.
Implementing FileSystemWatcher in C
Below is a complete implementation showing how to initialize, configure, and process file notifications in a console application.
using System;
using System.IO;
class Program
{
static void Main()
{
string directoryToWatch = @"C:\MonitoredFolder";
if (!Directory.Exists(directoryToWatch))
{
Directory.CreateDirectory(directoryToWatch);
}
using var watcher = new FileSystemWatcher(directoryToWatch);
// Configure what changes to watch
watcher.NotifyFilter = NotifyFilters.FileName
| NotifyFilters.DirectoryName
| NotifyFilters.LastWrite
| NotifyFilters.Size;
// Monitor only text files (use "*.*" for all files)
watcher.Filter = "*.txt";
watcher.IncludeSubdirectories = false;
// Attach event handlers
watcher.Created += OnCreated;
watcher.Changed += OnChanged;
watcher.Deleted += OnDeleted;
watcher.Renamed += OnRenamed;
watcher.Error += OnError;
// Start listening
watcher.EnableRaisingEvents = true;
Console.WriteLine($"Watching directory: {directoryToWatch}. Press 'q' to exit.");
while (Console.ReadKey().KeyChar != 'q') ;
}
private static void OnCreated(object sender, FileSystemEventArgs e) =>
Console.WriteLine($"[Created] {e.FullPath}");
private static void OnChanged(object sender, FileSystemEventArgs e) =>
Console.WriteLine($"[Changed] {e.FullPath}");
private static void OnDeleted(object sender, FileSystemEventArgs e) =>
Console.WriteLine($"[Deleted] {e.FullPath}");
private static void OnRenamed(object sender, RenamedEventArgs e) =>
Console.WriteLine($"[Renamed] {e.OldFullPath} -> {e.FullPath}");
private static void OnError(object sender, ErrorEventArgs e) =>
Console.WriteLine($"[Error] {e.GetException().Message}");
}Common Challenges and Best Practices
1. Handling Duplicate Events
Operating systems often trigger multiple Changed
notifications for a single file operation (for example, when file
contents and metadata like LastWriteTime are written in
separate steps).
To prevent redundant processing:
- Implement a short debounce mechanism using a memory cache or a
Dictionarythat tracks the last processed timestamp for a file. - Ignore any duplicate events for the same file that arrive within a short threshold (e.g., 200–500 milliseconds).
2. File Locking
When the Created event fires, the application that
created the file may still have an exclusive write lock on it.
Attempting to open or read the file immediately can throw an
IOException.
To resolve this:
- Wrap file access inside a retry loop with exponential backoff.
- Catch
IOExceptionand delay for a few hundred milliseconds before attempting to read again.
3. Preventing Buffer Overflows
FileSystemWatcher relies on an internal buffer to hold
events before delivering them to your code. If thousands of files change
simultaneously and your event handlers take too long to complete, the
buffer overflows, and changes are lost.
To avoid buffer drops:
- Keep event handlers extremely lightweight: offload file processing
to a background worker or a channel
(
System.Threading.Channels.Channel<T>). - Increase the buffer size via
watcher.InternalBufferSize(default is 8 KB, can be increased up to 64 KB) if burst file activity is expected. - Always subscribe to the
Errorevent to detect when a buffer overflow occurs.