Skip to content

FileSystemEventArgs constructor throws with empty directory value #68566

@jeffhandley

Description

@jeffhandley

Description

Before .NET 7, the FileSystemEventArgs constructor allowed an empty directory string to be specified without throwing. Starting in .NET 7, specifying an empty string for the directory throws an ArgumentException.

This breaks a scenario where a file name without a directory is passed to Path.GetDirectoryName, and that return value is passed into the FileSystemEventArgs or RenamedEventArgs.

Reproduction Steps

Minimal repro:

var eventArgs = new FileSystemEventArgs(WatcherChangeTypes.Created, "", null);

Test cases that illustrate the change in behavior compared to .NET 6:

var paths = new List<(string directory, string? file)>();
paths.Add(("", null));
paths.Add(("", ""));
paths.Add(("", "foo.txt"));
paths.Add((@"D:\", null));
paths.Add((@"D:\", ""));
paths.Add((@"D:\", "foo.txt"));
paths.Add((@"D:\git", null));
paths.Add((@"D:\git", ""));
paths.Add((@"D:\git", "foo.txt"));

foreach ((string d, string? f) in paths)
{
    string? argsName;
    string argsFullPath;

    try
    {
        var fileArgs = new FileSystemEventArgs(WatcherChangeTypes.Created, d, f);
        argsName = $"\"{fileArgs.Name}\"";
        argsFullPath = $"\"{fileArgs.FullPath}\"";
    }
    catch (Exception ex)
    {
        argsName = "throw";
        argsFullPath = ex.GetType().Name;
    }

    Console.WriteLine($"{"\"" + d + "\"",-12}{"\"" + f + "\"",-12}{argsName,-12}{argsFullPath}");
}

Expected behavior

In the minimal repro, the constructor should not throw.

  • eventArgs.Name should be ""
  • eventArgs.FullPath should be the current process directory, with a trailing directory separator character.

With the test cases, the output should be the following, where ~ is a placeholder representing the process's current directory.

""          ""          ""          "~\"
""          ""          ""          "~\"
""          "foo.txt"   "foo.txt"   "~\foo.txt"
"D:\"       ""          ""          "D:\"
"D:\"       ""          ""          "D:\"
"D:\"       "foo.txt"   "foo.txt"   "D:\foo.txt"
"D:\git"    ""          ""          "D:\git\"
"D:\git"    ""          ""          "D:\git\"
"D:\git"    "foo.txt"   "foo.txt"   "D:\git\foo.txt"

Actual behavior

The constructor throws ArgumentException when the directory is "".

The output from the test is:

""          ""          throw       ArgumentException
""          ""          throw       ArgumentException
""          "foo.txt"   throw       ArgumentException
"D:\"       ""          ""          "D:\"
"D:\"       ""          ""          "D:\"
"D:\"       "foo.txt"   "foo.txt"   "D:\foo.txt"
"D:\git"    ""          ""          "D:\git\"
"D:\git"    ""          ""          "D:\git\"
"D:\git"    "foo.txt"   "foo.txt"   "D:\git\foo.txt"

Regression?

Yes, this is a regression from .NET 6. This regression was introduced in #63051. That change made a deliberate breaking change to the FileSystemEventArgs.FullPath property's value, but it wasn't intended for the constructor to throw when directory is an empty string.

Instead of calling Path.Join(Path.GetFullPath(directory), name), we likely need to call Path.GetFullPath(Path.Join(directory, name)), as this will gracefully handle the empty directory.

Known Workarounds

A possible, but frustrating workaround, would be to check if directory is an empty string, and specify "." instead.

Configuration

No response

Other information

This was reported by an internal team at Microsoft as a .NET 7 adoption blocker because they have a lot of code using this pattern.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions