Path Types
Overview
Every path in Singulink.IO.FileSystem is represented by an interface. The interface tells you, statically, two independent facts about the path:
- Is it absolute or relative?
- Does it point to a file or a directory?
The combination produces four concrete interfaces (one of which every path object implements) plus three abstractions you can use when only one of the two facts matters.
The Interface Hierarchy
The hierarchy splits along two independent axes and combines into four leaf interfaces. Each leaf is the type used in practice; the others exist so your APIs can ask for exactly as much as they need.
| Path kind | Implements |
|---|---|
IAbsoluteFilePath |
IAbsolutePath + IFilePath |
IAbsoluteDirectoryPath |
IAbsolutePath + IDirectoryPath |
IRelativeFilePath |
IRelativePath + IFilePath |
IRelativeDirectoryPath |
IRelativePath + IDirectoryPath |
The two axes:
- Absolute vs relative: exposed by
IAbsolutePathandIRelativePath. Both extendIPath. - File vs directory: exposed by
IFilePathandIDirectoryPath. Both extendIPath.
Every concrete path implements one of the four leaves and inherits members from both axes. Use the most specific interface you can in your APIs; it's how the type system catches mistakes for you.
Members by Interface
IPath
The common base of every path. Members:
Name: the final segment (file or directory name).PathDisplay: friendly string suitable for display, logs and round-trippable serialization.PathFormat: the PathFormat of the path (Windows, Unix or Universal).HasParentDirectory,ParentDirectory: walk upward.IsRooted:truefor absolute paths and Windows rooted-relative paths (e.g.\Some\Path).Equals/==/!=: see Equality below.ToString(): diagnostic only; never use this for I/O.
IAbsolutePath
Adds members that only make sense for fully-qualified paths:
PathExport: the only string form safe to hand to non-library APIs.IsUnc:truefor UNC paths (Windows only).Exists: convenience boolean.State: richer status, returns one of the EntryState values:Exists,ParentExists,ParentDoesNotExistorWrongType.Attributes(get/set),CreationTime[Utc],LastAccessTime[Utc],LastWriteTime[Utc].RootDirectory,ParentDirectory(narrowed toIAbsoluteDirectoryPath).GetInfo(): returns a CachedEntryInfo.GetLastExistingDirectory(): walks up the path until a directory that exists is found.
IRelativePath
Adds:
ToPathFormat(format, options): convert a relative path between formats (e.g. Windows ↔ Universal).ParentDirectory(narrowed toIRelativeDirectoryPath).
IFilePath
Adds:
NameWithoutExtension,Extension.WithExtension(ext, options): replace the trailing extension.AddExtension(ext, options): append an extension, preserving any existing one.
See File Names and Extensions.
IDirectoryPath
Adds path-combination members and the + operator:
Combine(IRelativeDirectoryPath / IRelativeFilePath / IRelativePath).CombineDirectory(span, [format,] options),CombineFile(span, [format,] options).
See Combining and Navigating Paths.
IAbsoluteDirectoryPath
Combines IAbsolutePath and IDirectoryPath. Adds:
IsRoot,IsEmpty.DriveType,FileSystem,AvailableFreeSpace,TotalFreeSpace,TotalSize: see Drive and Disk Information.Create(),Delete(recursive, ignoreNotFound).GetInfo()returning CachedDirectoryInfo.- A full set of enumeration methods: see Searching and Enumeration.
IAbsoluteFilePath
Combines IAbsolutePath and IFilePath. Adds:
IsReadOnly,Length.OpenStream(...),OpenAsyncStream(...).CopyTo,MoveTo,Replace,Delete.GetInfo()returning CachedFileInfo.
See Working with Files.
IRelativeFilePath / IRelativeDirectoryPath
Combine IRelativePath with IFilePath / IDirectoryPath. Their members are the union of the two parents, narrowed to relative return types.
Why Strong Typing
Static typing catches at compile time the bugs you'd otherwise hit at runtime. A few examples:
void OpenLog(IAbsoluteFilePath path);
OpenLog(someDirectoryPath); // compile error: directory is not a file
OpenLog(someRelativeFilePath); // compile error: relative is not absolute
The library applies the same discipline internally: file system operations only exist on absolute paths, so a relative path can never accidentally be opened against the current working directory.
Pattern Matching
When you don't know statically which kind of path you have (e.g. you used FilePath.Parse which returns IFilePath), use pattern matching:
IFilePath file = FilePath.Parse(userInput);
if (file is IAbsoluteFilePath absolute)
{
using FileStream s = absolute.OpenStream();
// ...
}
else
{
IRelativeFilePath relative = (IRelativeFilePath)file;
IAbsoluteFilePath resolved = DirectoryPath.GetCurrent() + relative;
// ...
}
Tip
If you know up front that a string must be absolute (or must be relative), call ParseAbsolute or ParseRelative directly. The return type is the specific interface, so no cast or pattern match is needed.
Equality
Two paths are equal when:
- They implement the same concrete type (e.g. both are
IAbsoluteFilePath). - Their PathFormat is the same.
- Their root segments compare equal case-insensitively (drive letter or UNC name).
- The remainder of the path compares equal case-sensitively.
var a = FilePath.ParseAbsolute(@"C:\Apps\MyApp\config.json");
var b = FilePath.ParseAbsolute(@"c:\Apps\MyApp\config.json");
var c = FilePath.ParseAbsolute(@"C:\apps\MyApp\config.json");
a == b; // true : root casing differs, but the root is case-insensitive
a == c; // false: non-root segments are case-sensitive
Note
Equality is textual. Two different paths that resolve to the same physical entry through symbolic links or case-insensitive file systems are not equal; equality reflects the path, not what it points to.
Next Steps
- Parsing Paths: turn strings into instances of these interfaces.
- Path Formats: understand the third dimension (
PathFormat) that affects every path. - Combining and Navigating Paths: build new paths from existing ones.