.NET provides a handy way to deal with monitoring different file system changes. In this article, we will discuss what FileSystemWatcher is, how to set it up, and how to configure it to observe various file system changes. In addition, we will take a look at the caveats of FileSystemWatcher.

To download the source code for this article, you can visit our GitHub repository.

Let’s dig in.

What is FileSystemWatcher?

FileSystemWatcher is a class in the System.IO namespace and it helps us monitor file system changes. It is composed of different properties that enable us to configure the event types we want to listen to. Also, we can apply file and directory filtering.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Let’s inspect how we can set up the FileSystemWatcher in our project:

public class TextFileManager
{
    private readonly FileSystemWatcher _fileSystemWatcher;

    public TextFileManager(string rootDirectory)
    {
        _fileSystemWatcher = new FileSystemWatcher(rootDirectory);
    }
}

We can initialize FileSystemWatcher simply by passing the path, we want to monitor.  Throughout this article, we will use our TextFileManager class to configure and work with FileSystemWatcher instance. Of course, you can always download our source code to see the full implementation.

FileSystemWatcher Filters

There is a wide range of file system changes we can monitor using FileSystemWatcher. However, we can have a scenario where we don’t want to monitor all kinds of changes in our file system. Instead, we want to monitor specific file changes or specific file types. This is where filters are useful because they help us narrow down the type of changes we monitor. FileSystemWatcher contains two properties Filter and NotifyFilter to configure filtering.

Filter

Filter is FileSystemWatcher property that enables us to monitor specific files by specifying a file pattern. The default Filter’s string value is "*.*", which means to monitor all files.

Let’s configure the type of files we want to monitor:

private void ConfigureFilters()
{
    _fileSystemWatcher.Filter = "*.txt";
}

Now _fileSystemWatcher monitors only text file changes.

NotifyFilter

NotifyFilter is another property of FileSystemWatcher that enables us to specify and monitor specific changes in the file system. We can configure NotifyFilter to listen to multiple types of changes using the bitwise OR operator("|"). By default, the value of NotifyFilter is a bitwise OR combination of LastWrite, DirectoryName, and FileName, but we can expand that:

private void ConfigureFilters()
{
    ...
    _fileSystemWatcher.NotifyFilter = NotifyFilters.Attributes
                                | NotifyFilters.CreationTime
                                | NotifyFilters.DirectoryName
                                | NotifyFilters.FileName
                                | NotifyFilters.LastAccess
                                | NotifyFilters.LastWrite
                                | NotifyFilters.Security
                                | NotifyFilters.Size;
}

By combining NotifyFilters enumeration we can expand the range of changes we monitor. The NotifyFilters enumeration values specify changes to monitor in a file or directory.

The  Attributes value adds the capability to monitor file or directory attribute changes. File or directory attributes are meta-data information that describes a file or a directory behavior (such as file visibility, modifiable, encryption, and more ).

CreationTime as its name suggests it enables us to monitor the file or a directory creation time.

To monitor read or open operation on a file or directory we can use LastAccess.

To monitor file or directory security settings (such as read-write permissions, execution, and share permission ) changes we simply add Security into the combination.

In addition, if we want to monitor file or directory size changes that can happen as a result of file modification, we simply add Size.

We can also monitor changes inside subdirectories by using the IncludeSubdirectories property:

private void ConfigureFilters()
{
    ...
    _fileSystemWatcher.IncludeSubdirectories = true;
    ...
}

FileSystemWatcher Events

FileSystemWatcher consists of 5 different event types we can use. Namely:  Created, Deleted, Renamed, Error, and Changed.

Created

We can configure it to trigger an event on the creation of a file or a directory:

private void ConfigureEvents()
{
    _fileSystemWatcher.Created += HandleCreated;
}

private void HandleCreated(object sender, FileSystemEventArgs e)
{
    Console.WriteLine($"Create: {e.FullPath}");
}

Deleted

We can configure it to trigger an event on the deletion of a file or directory:

private void ConfigureEvents()
{
    _fileSystemWatcher.Deleted += HandleDeleted;
    ...
}

private void HandleDeleted(object sender, FileSystemEventArgs e)
{
    Console.WriteLine($"Delete: {e.FullPath}");
}

Changed

We can configure it to trigger an event when there is a change to the size, system attributes, the last update, the last access time, or permission of a file or directory:

private void ConfigureEvents()
{
    _fileSystemWatcher.Changed += HandleChanged;
    ...
}

private void HandleChanged(object sender, FileSystemEventArgs e)
{
    Console.WriteLine($"Change: {Enum.GetName(e.ChangeType)} {e.FullPath}");
}

The event handlers of  Created, Deleted and Changed have similar method signatures.

Renamed

We can use it to trigger an event when there is a file name or directory name change:

private void ConfigureEvents()
{
    _fileSystemWatcher.Renamed += HandleRenamed;
   ...
}

private void HandleRenamed(object sender, RenamedEventArgs e)
{
    Console.WriteLine($"Rename: {e.OldName} => {e.Name}");
}

Renamed event handlers have a different method signature from Created, Deleted, and Changed event handlers. The second argument of type RenamedEventArgs contains the old name and new name properties of the affected file or directory.

Error

For different reasons FileSystemWatcher could reach a state where it can no longer monitor file system changes in the specified path. To trigger an event in this scenario we use the Error event handler:

private void ConfigureEvents()
{
    _fileSystemWatcher.Error += HandleError; 
    ...
}

private void HandleError(object sender, ErrorEventArgs e)
{
    Console.WriteLine($"Error: {e.GetException().Message}");
}

Finally, to enable _fileSystemWatcher to raise events, we have to set EnableRaisingEvents property to true:

private void ConfigureEvents()
{
    ...
    _fileSystemWatcher.EnableRaisingEvents = true;
}

FileSystemWatcher events are raised to corresponding file system changes inside the path being monitored and in accordance with the filters. It is important to note that common file system operations (such as move or copy) might trigger more than one event.

FileSystemWatcher Caveats

FileSystemWatcher makes it easy to monitor our file system and trigger events accordingly. However, it is important to keep in mind when the buffer capacity is exceeded FileSystemWatcher can miss an event. The default buffer size is 8 KB, but we can set it to any value between 4 KB and 64 KB using InternalBufferSize property. The buffer is storage for all system notifications that relate to file changes and exclude file names. Each event can use up to 16 bytes of memory. If numerous changes take place in a short time it can cause a buffer overflow, as a result, FileSystemWatcher misses subsequent events.

To avoid buffer overflow:

  • Minimize the types of changes we want to monitor using NotifyFilter and IncludeSubdirectories
  • Monitor only the files that are of interest by specifying the file pattern, more importantly, avoid monitoring files with long names as they have a high contribution to filling up the buffer
  • To have more room in the buffer, increase the buffer size by setting InternalBufferSize property
  • Keep the event handler code to the possible minimum

Conclusion

In this article, we have learned how we can monitor a file system using FileSystemWatcher, how to configure the different events, ways of filtering, and finally the caveats and recommendations.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!