Working with Files
Overview
File operations live on IAbsoluteFilePath. Relative file paths can describe a file but cannot perform I/O; combine them with an absolute directory first (see Combining and Navigating Paths).
Every method described here can throw IOException (or one of its subtypes: FileNotFoundException, DirectoryNotFoundException, UnauthorizedIOAccessException, etc.). See Exception Handling for patterns.
Existence Checks
Exists
Quick boolean:
if (file.Exists)
file.Delete();
State
For richer information, use IAbsolutePath.State. It returns one of four EntryState values:
| Value | Meaning |
|---|---|
Exists |
The file exists. |
ParentExists |
The file does not exist, but its parent directory does. |
ParentDoesNotExist |
Neither the file nor its parent directory exist. |
WrongType |
A different kind of entry (e.g. a directory) exists at this path. |
switch (file.State)
{
case EntryState.Exists: /* open and read */ break;
case EntryState.ParentExists: /* prompt user, can create */ break;
case EntryState.ParentDoesNotExist: /* would need to create the directory tree */ break;
case EntryState.WrongType: /* a directory is here: refuse */ break;
}
Tip
Use State over Exists when your error handling needs to distinguish between "missing" and "wrong type". WrongType in particular catches a class of bugs that System.IO's boolean checks silently obscure.
Opening Streams
OpenStream
FileStream OpenStream(
FileMode mode = FileMode.Open,
FileAccess access = FileAccess.ReadWrite,
FileShare share = FileShare.None,
int bufferSize = 4096,
FileOptions options = FileOptions.None);
Always wrap the returned stream in using:
using FileStream stream = configFile.OpenStream(FileMode.Open, FileAccess.Read, FileShare.Read);
using StreamReader reader = new(stream);
string text = reader.ReadToEnd();
For writes, choose the appropriate FileMode:
FileMode |
Behavior |
|---|---|
Open |
File must exist; throws otherwise. |
OpenOrCreate |
Open if exists, create if not. |
Create |
Always create (truncates if exists). |
CreateNew |
Create; throw if exists. |
Append |
Open for append (write only); create if missing. |
Truncate |
Open and truncate; file must exist. |
OpenAsyncStream
Identical signature to OpenStream but always sets FileOptions.Asynchronous:
using FileStream stream = file.OpenAsyncStream(FileMode.Create, FileAccess.Write);
await stream.WriteAsync(buffer);
Note
The OS may not actually support asynchronous I/O for the underlying handle, in which case the runtime falls back to synchronous internally. The Asynchronous option only opts in to true async when the platform allows it.
File Properties
Length and IsReadOnly
long bytes = file.Length;
bool readOnly = file.IsReadOnly;
file.IsReadOnly = true; // toggle the read-only attribute
Attributes and Timestamps
Inherited from IAbsolutePath:
file.Attributes; // FileAttributes
file.CreationTime; // local time
file.CreationTimeUtc;
file.LastAccessTime;
file.LastWriteTime;
file.LastWriteTimeUtc;
file.Attributes |= FileAttributes.Hidden;
file.LastWriteTimeUtc = DateTime.UtcNow;
Tip
Each attribute/timestamp access touches the file system. If you'll read several at once, prefer file.GetInfo() which fetches everything in a single call. See Cached Entry Info.
Copy, Move, Replace
CopyTo
file.CopyTo(destination); // throws if destination exists
file.CopyTo(destination, overwrite: true);
MoveTo
file.MoveTo(destination);
file.MoveTo(destination, overwrite: true);
Note
MoveTo and CopyTo accept any IAbsoluteFilePath as the destination, including across directories or drives. The destination's parent must already exist; call destination.ParentDirectory.Create() first if needed.
Replace
Atomically replaces the contents of an existing file with this file, optionally producing a backup of the replaced file:
newFile.Replace(originalFile, backupFile: null);
newFile.Replace(originalFile, backupFile: backupPath, ignoreMetadataErrors: true);
Replace is the recommended way to atomically commit changes: write to a temporary file, then call Replace to swap it into place.
Deleting
file.Delete(); // ignores not-found by default
file.Delete(ignoreNotFound: false); // throws FileNotFoundException if absent
Putting It Together
Atomic write pattern:
IAbsoluteFilePath finalPath = appBase.CombineFile("config/app.json");
IAbsoluteFilePath tempPath = finalPath.AddExtension(".tmp");
finalPath.ParentDirectory.Create();
using (FileStream s = tempPath.OpenStream(FileMode.Create, FileAccess.Write))
using (StreamWriter w = new(s))
{
w.Write(serializedJson);
}
if (finalPath.Exists)
tempPath.Replace(finalPath, backupFile: null);
else
tempPath.MoveTo(finalPath);
Cached Snapshots
If you want a single consistent view of a file's metadata (size, attributes, timestamps), call GetInfo() to obtain a CachedFileInfo. See Cached Entry Info.
Next Steps
- Working with Directories: many file workflows start by creating a directory.
- Searching and Enumeration: find the files you want to operate on.
- Exception Handling: catch I/O errors cleanly.