Summary#
Path is C#'s System.IO.Path, spelled as in C# and answering what .NET answers on Linux, where Osyrin runs:
Path.Combine("invoices", "2026", "a.pdf"); // "invoices/2026/a.pdf"
Path.GetExtension("report.tar.gz"); // ".gz"
Path.GetFileNameWithoutExtension("/x/y.z.w"); // "y.z"
Path.GetDirectoryName("invoices/2026/a.pdf"); // "invoices/2026"
Path.ChangeExtension("a.txt", "md"); // "a.md"
Path.GetFullPath("../b.txt", "/app/docs"); // "/app/b.txt"Signature#
string Path.Combine(string a, string b[, string c[, string d]]) // also Path.Combine(params string[])
string Path.Join(string? a, string? b[, …]) // like Combine, but a rooted part does not restart
string? Path.GetFileName(string? path)
string? Path.GetFileNameWithoutExtension(string? path)
string? Path.GetExtension(string? path) // ".gz" — with the dot; "" when there is none
bool Path.HasExtension(string? path)
string? Path.ChangeExtension(string? path, string? extension)
string? Path.GetDirectoryName(string? path) // null for "/" (a root) and for null
string? Path.GetPathRoot(string? path) // "/" or ""
bool Path.IsPathRooted(string? path)
bool Path.IsPathFullyQualified(string path)
bool Path.EndsInDirectorySeparator(string? path)
string Path.TrimEndingDirectorySeparator(string path)
string Path.GetFullPath(string path, string basePath) // normalizes `.`, `..` and `//` against a base
string Path.GetFullPath(string path) // a ROOTED path only — see below
string Path.GetRelativePath(string relativeTo, string path) // both rooted
char Path.DirectorySeparatorChar // '/'
char Path.AltDirectorySeparatorChar // '/' — Linux has no second separator
char Path.VolumeSeparatorChar // '/' — nor a drive letter's ':'
char Path.PathSeparator // ':'
char[] Path.GetInvalidFileNameChars() // '\0' and '/'
char[] Path.GetInvalidPathChars() // '\0'Description#
A path here is text: nothing in Path touches storage, so it needs no capability and no use. It is how an app
builds the names it hands to File.WriteAllBytes(..., about: row) (File.WriteAllText(Path.Combine("exports", name), …)), takes apart an
upload's name, or picks the extension off a URL's last segment.
The rules are .NET's on Linux — / separates, a path is rooted when it starts with one, nothing is a drive, and
: separates a list of paths. So all three separator constants are '/': Path.AltDirectorySeparatorChar is the
same character because Linux has no second separator, and Path.VolumeSeparatorChar too because there is no drive
letter to end with ':'. Code written for Windows that splits on either keeps working. Path.Combine restarts at a rooted part (Combine("docs", "/abs", "a.txt") is
"/abs/a.txt") and Path.Join does not; GetExtension answers the dot too, and "" for "a."; GetDirectoryName
collapses doubled separators and answers null for a root. The same calls give the same answers in a server function
and in the browser.
An app has no current directory. It runs on a shared host, and the host's working directory is not the app's, so
the two members C# resolves against it — Path.GetFullPath(relative) and Path.GetRelativePath over a relative path —
throw PlatformNotSupportedException (.NET's own shape for a member a platform does not have) and name the fix: say
what the path is relative to with Path.GetFullPath(path, basePath). A path that is already rooted needs no current
directory and normalizes exactly as in C#.
Examples#
Name an export inside the app's storage, keeping the upload's own extension:
/// "exports/2026-10/invoice-17.pdf"
string ExportName(string month, int number, string uploadedAs) =>
Path.Combine("exports", month, "invoice-" + number + Path.GetExtension(uploadedAs));
/// "y.z" — the name without its LAST extension.
string Stem(string path) => Path.GetFileNameWithoutExtension(path);
/// "/app/b.txt" — `..` and `.` resolved against the base you name.
string Resolved(string path) => Path.GetFullPath(path, "/app/docs");
/// "../c/d"
string Between(string from, string to) => Path.GetRelativePath(from, to);[Test]
void Every_separator_is_a_slash() {
Assert.Equal('/', Path.DirectorySeparatorChar);
Assert.Equal('/', Path.AltDirectorySeparatorChar);
Assert.Equal('/', Path.VolumeSeparatorChar);
Assert.Equal(':', Path.PathSeparator);
}See also#
- File.WriteAllBytes(..., about: row) — read and write the files these paths name
- Uri — take a URL apart (
new Uri(url).AbsolutePath) - Encoding — Base64, hex, text encodings, URL, HTML — text ↔ bytes, for a file's contents