Click here to Skip to main content
15,890,609 members
Articles / Programming Languages / C#
Tip/Trick

Extending FileSystemWatcher to Use Regular Expression (Regex) Filters

Rate me:
Please Sign up or sign in to vote.
4.67/5 (2 votes)
5 Mar 2016CPOL3 min read 16K   210   8   4
Extends FileSystemWatcher to use Regex filters

Introduction

For a project at work, I needed to watch a folder for file name changes within it. .NET provides the FileSystemWatcher object to provide an event based option for identifying when directory or file changes occur. The issue I had was that it doesn't allow for the use of Regular Expressions (Regex) based filters and instead allow only the standard *.<Something> type of file filtering. Since I was already using the FileSystemWatcher in my project, I opted to extend its functionality.

Background

First, I started by looking at the constructors available for the FileSystemWatcher class. I noticed that if a pattern is not passed to filter by a default filter of *.* is used. So I started to create my new class that I named FileSystemWatcherEx that inherits from FileSystemWatcher.

C#
/// <summary>
/// Class FileSystemWatcherEx inherits from <see cref="FileSystemWatcher"/> 
/// but adds the ability to use <see cref="Regex"/> ass the filter.
/// </summary>
/// <seealso cref="System.IO.FileSystemWatcher" />
public class FileSystemWatcherEx : FileSystemWatcher {

Constructors

Then I added a new constructor that allows two parameters, a string path, and Regex pattern. The path is the folder that we will be watching and the pattern is what we will match the filenames against to see if they are the ones we want or not. We also need to re-create the base constructors to avoid any unnecessary backward compatibility issues.

C#
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="FileSystemWatcherEx"/> class.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="pattern">The pattern.</param>
public FileSystemWatcherEx(string path, Regex pattern) : base(path)
{
    RegexPattern = pattern;
}
/// *** NOTE: Below is required for backward compatibility ***
/// <summary>
/// Initializes a new instance of the <see cref="FileSystemWatcherEx"/> class.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="filter">The filter.</param>
public FileSystemWatcherEx(string path, string filter) : base(path, filter) {}

/// <summary>
/// Initializes a new instance of the <see cref="FileSystemWatcherEx"/> class.
/// </summary>
/// <param name="path">The path.</param>
public FileSystemWatcherEx(string path) : base(path) {}

/// <summary>
/// Initializes a new instance of the <see cref="FileSystemWatcherEx"/> class.
/// </summary>
public FileSystemWatcherEx():base(){ }
#endregion

Note: Not adding the constructors that call the base FileSystemWatcher class will effectively hide those class creation options from caller of our FileSystemWatcherEx class.

Properties

The only property I need to add is a Regex object I named RegexPattern.

C#
#region Properties & Indexers

/// <summary>
/// Gets or sets the regex pattern.
/// </summary>
/// <value>The regex pattern.</value>
public Regex RegexPattern { get; set; }

#endregion

Events

Next, I wanted to hide a few of the events that the FileSystemWatcher class provides so that I could do some extra work inside my new class before they are fired. I also added my own events that will only be used directly internally in my class. When a caller subscribes to the Changed, Created, Renamed or Deleted events in my FileSystemWatcher class, it will subscribe them to my corresponding internal event as well as instruct my class to subscribe to the base FileSystemWatcher class internally.

C#
/// <summary>
/// Occurs when [changed].
/// </summary>
public new event FileSystemEventHandler Changed
{
    add
    {
        IsChanged += value;
        base.Changed += FileSystemWatcherEx_Changed;
    }
    remove
    {
        IsChanged -= value;
        base.Created -= FileSystemWatcherEx_Changed;
    }
}

/// <summary>
/// Occurs when [created].
/// </summary>
public new event FileSystemEventHandler Created
{
    add
    {
        IsCreated += value;
        base.Created += FileSystemWatcherEx_Created;
    }
    remove
    {
        IsCreated -= value;
        base.Created -= FileSystemWatcherEx_Created;
    }
}

/// <summary>
/// Occurs when [deleted].
/// </summary>
public new event FileSystemEventHandler Deleted
{
    add
    {
        IsDeleted += value;
        base.Deleted += FileSystemWatcherEx_Deleted;
    }
    remove
    {
        IsDeleted -= value;
        base.Deleted -= FileSystemWatcherEx_Deleted;
    }
}

/// <summary>
/// Occurs when [renamed].
/// </summary>
public new event RenamedEventHandler Renamed
{
    add
    {
        IsRenamed += value;
        base.Renamed += FileSystemWatcherEx_Renamed;
    }
    remove
    {
        IsRenamed -= value;
        base.Renamed -= FileSystemWatcherEx_Renamed;
    }
}

/// <summary>
/// Occurs when [is changed].
/// </summary>
private event FileSystemEventHandler IsChanged;

/// <summary>
/// Occurs when [is created].
/// </summary>
private event FileSystemEventHandler IsCreated;

/// <summary>
/// Occurs when [is deleted].
/// </summary>
private event FileSystemEventHandler IsDeleted;

/// <summary>
/// Occurs when [is renamed].
/// </summary>
private event RenamedEventHandler IsRenamed;

The new keyword is used to hide properties in the base FileSystemWatcher class.

When events are raised internally from the FileSystemWatcher, we check to see if the RegEx was provided. If it wasn't, then we assume a traditional file pattern was. For a traditional filter, we simply raise the internal event that we had the caller subscribe to. If a Regex was provided, we call a helper method to see if the Regex matches our altered object and if so we again raise the internal events that the caller subscribed to.

C#
/// <summary>
/// Handles the Changed event of the FileSystemWatcherEx control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="FileSystemEventArgs"/> 
/// instance containing the event data.</param>
private void FileSystemWatcherEx_Changed(object sender, FileSystemEventArgs e)
{
    if (RegexPattern == null)
        IsChanged?.Invoke(sender, e);
    else if (MatchesRegex(e.Name))
        IsChanged?.Invoke(sender, e);
}

/// <summary>
/// Handles the Created event of the FileSystemWatcherEx control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="FileSystemEventArgs"/> 
/// instance containing the event data.</param>
private void FileSystemWatcherEx_Created(object sender, FileSystemEventArgs e)
{
    if (RegexPattern == null)
        IsCreated?.Invoke(sender, e);
    else if (MatchesRegex(e.Name))
        IsCreated?.Invoke(sender, e);
}

/// <summary>
/// Handles the Deleted event of the FileSystemWatcherEx control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="FileSystemEventArgs"/> 
/// instance containing the event data.</param>
private void FileSystemWatcherEx_Deleted(object sender, FileSystemEventArgs e)
{
    if (RegexPattern == null)
        IsDeleted?.Invoke(sender, e);
    else if (MatchesRegex(e.Name))
        IsDeleted?.Invoke(sender, e);
}

/// <summary>
/// Handles the Renamed event of the FileSystemWatcherEx control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="RenamedEventArgs"/> 
/// instance containing the event data.</param>
private void FileSystemWatcherEx_Renamed(object sender, RenamedEventArgs e)
{
    if (RegexPattern == null)
        IsRenamed?.Invoke(sender, e);
    else if (MatchesRegex(e.Name))
        IsRenamed?.Invoke(sender, e);
}

Regex Matching Helper Method

C#
/// <summary>
/// Matches the regex.
/// </summary>
/// <param name="file">The file.</param>
/// <returns><c>true</c> if regex matches the file, <c>false</c> otherwise.</returns>
private bool MatchesRegex(string file)
{
    return RegexPattern.IsMatch(file);
}

That's it, we now have a new FileSystemWatcherEx class that acts just like the regular FileSystemWatcher but also adds the ability to use a Regex as the filter.

Using the Code

To use the class, just create an instance of it passing the folder path to watch and the Regex you would like to use. Here, I am accepting a file that has an extension with a number (IE *.1, *.2, *.123, etc) in my temp folder and then changing the extension to "GotIT".

C#
private void button1_Click(object sender, EventArgs e)
{
    FileSystemWatcherEx watcher = new FileSystemWatcherEx("c:\\temp",new Regex(@"(.*\.\d{1,})"));
    watcher.EnableRaisingEvents = true;
    watcher.Renamed += Watcher_Renamed;
}

private void Watcher_Renamed(object sender, RenamedEventArgs e)
{
    File.Move(e.FullPath,Path.ChangeExtension(e.FullPath, "GotIT"));
}

FYI: In case you haven't found a way you like to create and test your regex, you might give the following a try https://regex101.com/r/cV1pQ2/1.

History

  • 5th March, 2016 - Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Systems / Hardware Administrator
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionFix design Pin
LOST_FREEMAN5-Mar-16 22:57
LOST_FREEMAN5-Mar-16 22:57 
From the design standpoint that is not a very good solution. One should not inherit from FileSystemWatcher, since there's nothing to override.
Also, instead of passing regex you could pass Func<string, bool="">, and just call that func in place of Matchex to generalize event filtering even further. Then you'd do new Watcher(dir, myRegex.IsMatch) or new Watcher(dir, fileName => Convert.ToInt32(fileName) < 100) or whatever filter you want.
AnswerRe: Fix design Pin
rleonard556-Mar-16 4:36
rleonard556-Mar-16 4:36 
GeneralRe: Fix design Pin
John Brett13-Mar-16 22:14
John Brett13-Mar-16 22:14 
GeneralRe: Fix design Pin
rleonard5514-Mar-16 2:29
rleonard5514-Mar-16 2:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.