Combining and Navigating Paths
Paths are immutable, so every "modification" returns a new path object. New paths are built from existing ones with the + operator, the combine methods on IDirectoryPath (Combine, CombineDirectory and CombineFile) and navigation members such as IPath.ParentDirectory and IAbsolutePath.RootDirectory. All combinations preserve type strength: combining an absolute directory with a relative file produces an absolute file, and so on.
The + Operator
IDirectoryPath defines + overloads for every relative path type:
IAbsoluteDirectoryPath baseDir = DirectoryPath.GetAppBase();
IRelativeDirectoryPath subDir = DirectoryPath.ParseRelative("logs");
IRelativeFilePath logFile = FilePath.ParseRelative("today.log");
IAbsoluteDirectoryPath logsDir = baseDir + subDir; // IAbsoluteDirectoryPath
IAbsoluteFilePath fullLogPath = logsDir + logFile; // IAbsoluteFilePath
IAbsoluteFilePath inOneStep = baseDir + (subDir + logFile);
Return types are inferred from the operands. Absolute + relative produces absolute; relative + relative produces relative.
Combine With a Pre-Parsed Path
When you already have a relative path object, use IDirectoryPath.Combine:
IAbsoluteFilePath result = baseDir.Combine(logFile);
This is identical to the + operator, just spelled out.
Combine With a String
When the relative segment is a string (e.g. read from configuration), use IDirectoryPath.CombineDirectory or IDirectoryPath.CombineFile. These overloads parse the string in one step:
IAbsoluteFilePath cfgFile = baseDir.CombineFile("config/app.json");
IAbsoluteDirectoryPath dataDir = baseDir.CombineDirectory("data");
By default, the string is parsed using the parent's IPath.PathFormat and PathOptions.NoUnfriendlyNames. Override either:
IAbsoluteFilePath portable = baseDir.CombineFile("data/users.json", PathFormat.Universal);
IAbsoluteDirectoryPath relaxed = baseDir.CombineDirectory(rawDir, PathOptions.None);
The format argument on these overloads is the PathFormat used to parse the string, which is always parsed as a relative path. The only formats that combine cleanly with the base are PathFormat.Universal (always works) and the base directory's own IPath.PathFormat (the default when no format is passed). Passing a different platform-specific format than the base throws unless the base itself is PathFormat.Universal. See Path Formats for the cross-format combine rules.
Tip
If you'll combine the same relative path more than once, parse it into an IRelativePath once and reuse it. Combining a parsed relative path is cheaper and skips re-validation.
Combining Untrusted Strings
The string overloads throw ArgumentException when the string is not a valid relative path. When that is an expected outcome, parse the string first with FilePath.TryParseRelative or DirectoryPath.TryParseRelative and combine the result:
var options = PathOptions.NoUnfriendlyNames | PathOptions.NoNavigation;
if (FilePath.TryParseRelative(userInput, options, out IRelativeFilePath? relative))
{
IAbsoluteFilePath target = baseDir + relative;
// ...
}
PathOptions.NoNavigation matters here. Without it, a relative path such as ../../x.txt parses successfully, and combining it can still throw if its .. segments walk past the root of the base directory.
Generic Combine
For code that works with arbitrary relative paths, IDirectoryPath.Combine accepting an IRelativePath returns the unifying base type (IPath, IAbsolutePath or IRelativePath depending on the receiver). Pattern match on the result if you need the specific type:
IAbsolutePath result = baseDir.Combine(someRelative);
if (result is IAbsoluteFilePath file) { /* ... */ }
Walking Upward: ParentDirectory
Every path has a IPath.ParentDirectory. For files it's always the containing directory; for directories it's null when the path is a root or otherwise has no parent (check IPath.HasParentDirectory first).
IAbsoluteFilePath cfg = FilePath.ParseAbsolute(@"C:\Apps\MyApp\config\app.json");
IAbsoluteDirectoryPath cfgDir = cfg.ParentDirectory; // C:\Apps\MyApp\config\
IAbsoluteDirectoryPath appDir = cfgDir.ParentDirectory!; // C:\Apps\MyApp\
A common pattern: ensure a file's directory exists before writing to it:
file.ParentDirectory.Create(); // creates the directory tree if missing
using var stream = file.OpenStream(FileMode.Create);
Walking to the Root
Use IAbsolutePath.RootDirectory (on absolute paths) to jump straight to the root, or loop with IPath.HasParentDirectory:
IAbsoluteDirectoryPath root = cfg.RootDirectory; // C:\
IDirectoryPath current = cfg.ParentDirectory;
while (current.HasParentDirectory)
current = current.ParentDirectory!;
GetLastExistingDirectory
When working with a path that may not exist yet, IAbsolutePath.GetLastExistingDirectory walks up the path until it finds a directory that does:
var maybeMissing = FilePath.ParseAbsolute(@"C:\Apps\NewApp\data\users.json");
IAbsoluteDirectoryPath existing = maybeMissing.GetLastExistingDirectory();
long free = existing.AvailableFreeSpace; // useful for pre-flight checks
See Drive and Disk Information for more on disk-space queries.
Navigation in Relative Paths
Relative paths can encode upward navigation with ... The library resolves these as far as possible during parsing:
DirectoryPath.ParseRelative("a/b/../c"); // "a/c/"
DirectoryPath.ParseRelative("../../shared"); // "../../shared/" (kept: can't resolve further)
Navigating past the root of an absolute path is always an error (see Parsing Paths).
Note
Every path is normalized as part of parsing. After parsing, the IPath.PathDisplay you see is what the library will use everywhere; there is no separate "canonical form".
Cross-Format Combines
Combining works seamlessly when one side uses PathFormat.Universal:
IRelativeFilePath portableCfg = FilePath.ParseRelative("config/app.json", PathFormat.Universal);
IAbsoluteDirectoryPath baseDir = DirectoryPath.GetAppBase(); // current format
IAbsoluteFilePath cfg = baseDir + portableCfg; // current format
Combining two specific formats that don't match (e.g. Windows + Unix) is an error. See the table in Path Formats.
Next Steps
Once you can build paths, these guides cover what to do with them:
- File Names and Extensions: manipulate the trailing segment of a path.
- Working with Directories: once you've combined a path, do something with it.