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
.
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
public FileSystemWatcherEx(string path, Regex pattern) : base(path)
{
RegexPattern = pattern;
}
public FileSystemWatcherEx(string path, string filter) : base(path, filter) {}
public FileSystemWatcherEx(string path) : base(path) {}
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
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.
public new event FileSystemEventHandler Changed
{
add
{
IsChanged += value;
base.Changed += FileSystemWatcherEx_Changed;
}
remove
{
IsChanged -= value;
base.Created -= FileSystemWatcherEx_Changed;
}
}
public new event FileSystemEventHandler Created
{
add
{
IsCreated += value;
base.Created += FileSystemWatcherEx_Created;
}
remove
{
IsCreated -= value;
base.Created -= FileSystemWatcherEx_Created;
}
}
public new event FileSystemEventHandler Deleted
{
add
{
IsDeleted += value;
base.Deleted += FileSystemWatcherEx_Deleted;
}
remove
{
IsDeleted -= value;
base.Deleted -= FileSystemWatcherEx_Deleted;
}
}
public new event RenamedEventHandler Renamed
{
add
{
IsRenamed += value;
base.Renamed += FileSystemWatcherEx_Renamed;
}
remove
{
IsRenamed -= value;
base.Renamed -= FileSystemWatcherEx_Renamed;
}
}
private event FileSystemEventHandler IsChanged;
private event FileSystemEventHandler IsCreated;
private event FileSystemEventHandler IsDeleted;
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.
private void FileSystemWatcherEx_Changed(object sender, FileSystemEventArgs e)
{
if (RegexPattern == null)
IsChanged?.Invoke(sender, e);
else if (MatchesRegex(e.Name))
IsChanged?.Invoke(sender, e);
}
private void FileSystemWatcherEx_Created(object sender, FileSystemEventArgs e)
{
if (RegexPattern == null)
IsCreated?.Invoke(sender, e);
else if (MatchesRegex(e.Name))
IsCreated?.Invoke(sender, e);
}
private void FileSystemWatcherEx_Deleted(object sender, FileSystemEventArgs e)
{
if (RegexPattern == null)
IsDeleted?.Invoke(sender, e);
else if (MatchesRegex(e.Name))
IsDeleted?.Invoke(sender, e);
}
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
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