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.
The two axes:
- Absolute vs relative: exposed by IAbsolutePath and IRelativePath. Both extend IPath.
- File vs directory: exposed by IFilePath and IDirectoryPath. Both extend IPath.
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, IPath. Members:
- Name: the final segment (file or directory name).
- PathDisplay: friendly string suitable for display, logs and round-trippable serialization. Non-empty directory paths always end with the format's separator; file paths never do (see Path Formats).
- 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, op_Equality and op_Inequality: see Equality below.
- ToString: diagnostic only; never use this for I/O.
IAbsolutePath
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, ParentDoesNotExist or WrongType.
- Attributes (get/set), CreationTime, CreationTimeUtc, LastAccessTime, LastAccessTimeUtc, LastWriteTime, LastWriteTimeUtc.
- RootDirectory, ParentDirectory (narrowed to IAbsoluteDirectoryPath).
- GetInfo: returns a CachedEntryInfo.
- GetLastExistingDirectory: walks up the path until a directory that exists is found.
IRelativePath
IRelativePath adds:
- ToPathFormat: convert a relative path between formats (e.g. Windows to/from Universal).
- ParentDirectory (narrowed to IRelativeDirectoryPath).
IFilePath
IFilePath adds:
- NameWithoutExtension, Extension.
- WithExtension: replace the trailing extension.
- AddExtension: append an extension, preserving any existing one.
See File Names and Extensions.
IDirectoryPath
IDirectoryPath adds path-combination members and the + operator:
- Combine with IRelativeDirectoryPath, IRelativeFilePath or IRelativePath.
- CombineDirectory, CombineFile.
See Combining and Navigating Paths.
IAbsoluteDirectoryPath
IAbsoluteDirectoryPath combines IAbsolutePath and IDirectoryPath. Adds:
- IsRoot, IsEmpty.
- DriveType, FileSystem, AvailableFreeSpace, TotalFreeSpace, TotalSize: see Drive and Disk Information.
- Create, Delete.
- MoveTo: move or rename the directory (same volume).
- GetInfo returning CachedDirectoryInfo.
- A full set of enumeration methods: see Searching and Enumeration.
IAbsoluteFilePath
IAbsoluteFilePath combines IAbsolutePath and IFilePath. Adds:
- IsReadOnly, Length.
- OpenStream, OpenAsyncStream.
- CopyTo, MoveTo, Replace, Delete.
- GetInfo returning CachedFileInfo.
See Working with Files.
IRelativeFilePath / IRelativeDirectoryPath
IRelativeFilePath and 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 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.