Extending FileSystemWatcher to Use Regular Expression (Regex) Filters






4.67/5 (2 votes)
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
.
/// <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.
#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
.
#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.
/// <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.
/// <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
/// <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
".
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