Comparing paths
editEquality and case sensitivity
FullPath upper = FullPath.GetTempPath() / "Reports" / "summary.txt";
FullPath lower = FullPath.GetTempPath() / "reports" / "summary.txt";
// == and Equals ignore case on Windows, macOS, iOS and tvOS, and are case-sensitive elsewhere
Assert.Equal(!FullPathComparer.Default.IsCaseSensitive, upper == lower);
// Choose the case sensitivity when the result must not depend on the operating system
Assert.True(upper.Equals(lower, ignoreCase: true));
Assert.False(upper.Equals(lower, ignoreCase: false));
Assert.True(FullPathComparer.CaseInsensitive.Equals(upper, lower));==,!=,EqualsandGetHashCodeuse FullPathComparer.Default. It compares the paths ordinally, and ignores case on Windows, macOS, iOS and tvOS only.Equals(other, ignoreCase),CompareTo(other, ignoreCase)andGetHashCode(ignoreCase)use the case sensitivity you choose.- The default follows the operating system, not the file system: a case-sensitive folder on Windows or a case-insensitive volume on Linux is not detected. Choose the case sensitivity explicitly when you know it.
Compare FullPath values, not their strings. fullPath == "C:\\temp" compares two strings ordinally and is case-sensitive on every operating system, and fullPath.Equals("C:\\temp") is always false. The analyzer reports both: see Comparisons and checks.
Collections and sorting
FullPathComparer implements IEqualityComparer<FullPath> and IComparer<FullPath>. Pass one of its instances to a HashSet, a Dictionary or a sort:
FullPath root = FullPath.GetTempPath();
// Use a FullPathComparer to choose the case sensitivity of a set or a dictionary
var visited = new HashSet<FullPath>(FullPathComparer.CaseInsensitive);
Assert.True(visited.Add(root / "Data"));
Assert.False(visited.Add(root / "data"));
// Sort paths with the same comparers
List<FullPath> paths = [root / "b.txt", root / "A.txt", root / "a.txt"];
paths.Sort(FullPathComparer.CaseSensitive);
Assert.Equal([root / "A.txt", root / "a.txt", root / "b.txt"], paths);| Comparer | Case sensitivity |
|---|---|
| FullPathComparer.Default | The one of the operating system |
| FullPathComparer.CaseSensitive | Case-sensitive |
| FullPathComparer.CaseInsensitive | Case-insensitive |
IsCaseSensitive tells which one a comparer is. Without a comparer, collections use FullPathComparer.Default, and the <, <=, > and >= operators compare with it too. Sorting is ordinal: it does not depend on the culture.
Keep a path inside a folder
IsChildOf tells whether a path is inside a folder, at any depth. Use it to check a path built from user input before you read or write the file:
FullPath uploads = FullPath.GetTempPath() / "uploads";
FullPath image = uploads / "images" / "logo.png";
Assert.True(image.IsChildOf(uploads));
// ".." is resolved before the check, so the path cannot escape the folder
FullPath escaped = uploads / "../secrets.txt";
Assert.False(escaped.IsChildOf(uploads));
// A folder is not its own child, and a sibling that starts with the same name is not a child
Assert.False(uploads.IsChildOf(uploads));
Assert.False((FullPath.GetTempPath() / "uploads-old").IsChildOf(uploads));- The check uses the case sensitivity of FullPathComparer.Default.
- It compares the paths only: it does not follow symbolic links. A link inside the folder can point outside of it; when this matters, compare the canonical paths: see Symbolic links.
- It throws an
InvalidOperationExceptionwhen the path is empty, and anArgumentExceptionwhen the folder is empty.
Relative paths
MakePathRelativeTo returns the path relative to a folder, as a string. It uses Path.GetRelativePath, so the result can start with .. segments:
FullPath project = FullPath.GetTempPath() / "project";
FullPath program = project / "src" / "Program.cs";
Assert.Equal(Path.Combine("src", "Program.cs"), program.MakePathRelativeTo(project));
Assert.Equal(Path.Combine("..", "..", "docs"), (project / "docs").MakePathRelativeTo(project / "src" / "app"));
Assert.Equal(".", project.MakePathRelativeTo(project));- The result uses the directory separator of the operating system.
- It is
.when both paths are equal, and the full path when the folder is empty. - It throws an
InvalidOperationExceptionwhen the path itself is empty.