用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/rudironsoni/Synaxis --skill dotnet-file-io命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
Routes .NET/C# work to domain skills. Loads coding-standards for code paths.
基于 SOC 职业分类
| name | dotnet-file-io |
| category | developer-experience |
| subcategory | cli |
| description | Performs file I/O. FileStream, RandomAccess, FileSystemWatcher, MemoryMappedFile, paths. |
| license | MIT |
| targets | ["*"] |
| tags | ["csharp","dotnet","skill"] |
| version | 0.0.1 |
| author | dotnet-agent-harness |
| invocable | true |
| claudecode | {"allowed-tools":["Read","Grep","Glob","Bash","Write","Edit"]} |
| codexcli | {"short-description":".NET skill guidance for csharp tasks"} |
| opencode | {"allowed-tools":["Read","Grep","Glob","Bash","Write","Edit"]} |
| copilot | {} |
| geminicli | {} |
| antigravity | {} |
File I/O patterns for .NET applications. Covers FileStream construction with async flags, RandomAccess API for thread-safe offset-based I/O, File convenience methods, FileSystemWatcher event handling and debouncing, MemoryMappedFile for large files and IPC, path handling security (Combine vs Join), secure temp file creation, cross-platform considerations, IOException hierarchy, and buffer sizing guidance.
Cross-references: [skill:dotnet-io-pipelines] for PipeReader/PipeWriter network I/O, [skill:dotnet-gc-memory] for POH and memory-mapped backing array GC implications, [skill:dotnet-performance-patterns] for Span/Memory basics and ArrayPool usage, [skill:dotnet-csharp-async-patterns] for async/await patterns used with file streams.
FileStream async methods (ReadAsync, WriteAsync) silently block the calling thread unless the stream is opened with
the async flag. This is the most common file I/O mistake in .NET code.
// CORRECT: async-capable FileStream
await using var fs = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize: 4096,
useAsync: true); // Required for true async I/O
byte[] buffer = new byte[4096];
int bytesRead = await fs.ReadAsync(buffer, cancellationToken);
```text
```csharp
// ALSO CORRECT: FileOptions overload
fs = FileStream(
path,
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize: ,
FileOptions.Asynchronous | FileOptions.SequentialScan);
```text
Without `useAsync: ` `FileOptions.Asynchronous`, the runtime emulates dispatching synchronous I/O to the
thread pool -- wasting a thread adding overhead.
```csharp
fs = FileStream(path, FileStreamOptions
{
Mode = FileMode.Open,
Access = FileAccess.Read,
Share = FileShare.Read,
Options = FileOptions.Asynchronous | FileOptions.SequentialScan,
BufferSize = ,
PreallocationSize = _048_576
});
```text
`PreallocationSize` reserves disk space upfront creating overwriting files, reducing filesystem fragmentation
writes.
---
`RandomAccess` provides , offset-based, thread-safe I/O. Unlike FileStream, it has no position
state, so multiple threads can read/write different offsets concurrently without synchronization.
```csharp
handle = File.OpenHandle(
path,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
FileOptions.Asynchronous);
[] buffer = [];
bytesRead = RandomAccess.ReadAsync(
handle, buffer, fileOffset: , cancellationToken);
[] buffer2 = [];
bytesRead2 = RandomAccess.ReadAsync(
handle, buffer2, fileOffset: , cancellationToken);
```text
```csharp
IReadOnlyList<Memory<>> buffers = []
{
[].AsMemory(),
[].AsMemory()
};
totalRead = RandomAccess.ReadAsync(
handle, buffers, fileOffset: , cancellationToken);
```text
| Scenario | Use |
| ------------------------------------------------------- | ------------ |
| Concurrent reads different offsets | RandomAccess |
| Sequential streaming reads/writes | FileStream |
| Index files, database pages, memory-mapped alternatives | RandomAccess |
| Integration Stream-based APIs | FileStream |
---
For small files streaming unnecessary, the `File` methods are simpler correct.
```csharp
content = File.ReadAllTextAsync(path, cancellationToken);
[] lines = File.ReadAllLinesAsync(path, cancellationToken);
( line File.ReadLinesAsync(path, cancellationToken))
{
ProcessLine(line);
}
File.WriteAllTextAsync(path, content, cancellationToken);
[] data = File.ReadAllBytesAsync(path, cancellationToken);
```text
| File size | Approach |
| --------- | ---------------------------------------------------------- |
| < MB | `File.ReadAllTextAsync` / `File.ReadAllBytesAsync` |
| - MB | `File.ReadLinesAsync` FileStream buffered reading |
| > MB | FileStream RandomAccess buffer management |
---
```csharp
watcher = FileSystemWatcher(directoryPath)
{
Filter = ,
NotifyFilter = NotifyFilters.FileName
| NotifyFilters.LastWrite
| NotifyFilters.Size,
IncludeSubdirectories = ,
EnableRaisingEvents =
};
watcher.Changed += OnChanged;
watcher.Created += OnCreated;
watcher.Deleted += OnDeleted;
watcher.Renamed += OnRenamed;
watcher.Error += OnError;
```text
{
FileSystemWatcher _watcher;
Channel<> _channel;
CancellationTokenSource _cts = ();
{
_channel = Channel.CreateBounded<>(
BoundedChannelOptions()
{
FullMode = BoundedChannelFullMode.DropOldest
});
_watcher = FileSystemWatcher(path, filter)
{
EnableRaisingEvents =
};
_watcher.Changed += (_, e) =>
_channel.Writer.TryWrite(e.FullPath);
}
{
linked = CancellationTokenSource
.CreateLinkedTokenSource(ct, _cts.Token);
seen = Dictionary<, DateTime>();
( path
_channel.Reader.ReadAllAsync(linked.Token))
{
now = DateTime.UtcNow;
(seen.TryGetValue(path, last)
&& now - last < debounce)
;
seen[path] = now;
path;
}
}
{
_cts.Cancel();
_watcher.Dispose();
_cts.Dispose();
}
}
```text
The buffer defaults to KB. When many changes occur rapidly, the buffer overflows events are lost.
Increase `InternalBufferSize` (max KB Windows) handle the `Error` .
```csharp
watcher.InternalBufferSize = _536;
watcher.Error += (_, e) =>
{
(e.GetException() InternalBufferOverflowException)
{
logger.LogWarning();
}
};
```text
| Platform | Backend | Notable behavior |
| -------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Windows | ReadDirectoryChangesW | Most reliable; supports `InternalBufferSize` up to KB |
| Linux | inotify | ; recursive watches one inotify watch per subdirectory |
| macOS | FSEvents (kqueue fallback) | Coarser granularity; may batch events slight delay |
---
Map a memory random access without read/write calls. Efficient large files that
fit memory -- the OS pages data needed.
```csharp
mmf = MemoryMappedFile.CreateFromFile(
path,
FileMode.Open,
mapName: ,
capacity: ,
MemoryMappedFileAccess.Read);
accessor = mmf.CreateViewAccessor(
offset: ,
size: ,
MemoryMappedFileAccess.Read);
accessor.Read<MyHeader>(position: , header);
stream = mmf.CreateViewStream(
offset: ,
size: ,
MemoryMappedFileAccess.Read);
```text
```csharp
mmf = MemoryMappedFile.CreateNew(
,
capacity: _048_576,
MemoryMappedFileAccess.ReadWrite);
accessor = mmf.CreateViewAccessor();
accessor.Write(, );
mmf2 = MemoryMappedFile.OpenExisting(
,
MemoryMappedFileRights.Read);
accessor2 = mmf2.CreateViewAccessor(
, , MemoryMappedFileAccess.Read);
= accessor2.ReadInt32();
```text
For GC implications of memory-mapped backing arrays POH usage, see [skill:dotnet-gc-memory].
---
`Path.Combine` silently discards the first argument the second argument a rooted path. This enables path
traversal attacks user input passed the second argument.
```csharp
basePath = ;
userInput = ;
result = Path.Combine(basePath, userInput);
result2 = Path.Join(basePath, userInput);
```text
```
{
fullBase = Path.GetFullPath(basePath);
fullPath = Path.GetFullPath(
Path.Join(fullBase, userPath));
(!fullPath.StartsWith(fullBase + Path.DirectorySeparatorChar,
StringComparison.OrdinalIgnoreCase)
&& !fullPath.Equals(fullBase, StringComparison.OrdinalIgnoreCase))
{
UnauthorizedAccessException(
);
}
fullPath;
}
```text
Use `Path.DirectorySeparatorChar` (platform-specific) `Path.AltDirectorySeparatorChar` instead of hardcoded `/`
`\\`. `Path.Join` `Path.Combine` handle separator normalization automatically.
---
`Path.GetTempFileName()` creates a zero- a predictable name pattern throws the temp directory
contains , `.tmp` files. Use `Path.GetRandomFileName()` instead.
```csharp
tempPath = Path.Join(
Path.GetTempPath(),
Path.GetRandomFileName());
fs = FileStream(
tempPath,
FileMode.CreateNew,
FileAccess.Write,
FileShare.None,
bufferSize: ,
FileOptions.Asynchronous | FileOptions.DeleteOnClose);
fs.WriteAsync(data, cancellationToken);
```text
`FileOptions.DeleteOnClose` ensures the temp removed the stream closed. On Windows, the OS guarantees
deletion the last handle closes. On Linux/macOS, deletion happens during `Dispose` = FileStream(path, FileStreamOptions
{
Mode = FileMode.Create,
Access = FileAccess.Write,
UnixCreateMode = UnixFileMode.UserRead | UnixFileMode.UserWrite
});
```text
On Windows, `UnixCreateMode` silently ignored. Do use it a security control Windows -- use ACLs Windows
security APIs instead.
| Platform | Lock type | Behavior |
| -------- | ---------------------- | -------------------------------------------------- |
| Windows | Mandatory | Other processes cannot read/write locked regions |
| Linux | Advisory (flock/fcntl) | Locks are cooperative -- processes can ignore them |
| macOS | Advisory (flock) | Same Linux -- cooperative |
`FileShare` flags control sharing Windows. On Linux/macOS, use `FileStream.Lock` advisory locking but be aware
that non-cooperating processes can bypass it.
---
```text
IOException
+-- FileNotFoundException
+-- DirectoryNotFoundException
+-- PathTooLongException
+-- DriveNotFoundException
+-- EndOfStreamException
+-- FileLoadException
```text
```csharp
{
fs = FileStream(path,
FileMode.Open, FileAccess.Read);
}
(IOException ex) (ex.HResult == (()))
{
logger.LogError(, path);
}
(IOException ex) (ex.HResult == (()))
{
logger.LogWarning(, path);
}
(UnauthorizedAccessException ex)
{
logger.LogError(, path);
}
```text
Write operations may succeed but buffer data memory. A disk-full condition can surface at `Flush` `Dispose` time
rather than at the `Write` call. Always check exceptions flush.
```csharp
fs = FileStream(path,
FileMode.Create, FileAccess.Write,
FileShare.None, , FileOptions.Asynchronous);
fs.WriteAsync(data, cancellationToken);
fs.FlushAsync(cancellationToken);
```text
---
Guidance based dotnet/runtime benchmarks FileStream implementation:
| File operation | Recommended buffer | Rationale |
| ----------------------------------- | ----------------------------------- | ------------------------------------------------------------ |
| ; FileStream |
| ; diminishing returns above KB |
| Network-; larger buffers waste read-ahead |
| FileStream sequential scan | KB + `FileOptions.SequentialScan` | OS read-ahead handles prefetching |
`FileOptions.SequentialScan` hints the OS to prefetch data ahead of the read position. It beneficial sequential
reads can degrade performance random access patterns.
---
**Do use FileStream methods without `useAsync: `** -- without the flag, `ReadAsync`/`WriteAsync`
dispatch synchronous I/O to the thread pool, blocking a thread adding overhead. Always pass `useAsync: `
`FileOptions.Asynchronous`.
**Do use `Path.Combine` untrusted input** -- `Path.Combine` silently discards the path the second
argument rooted, enabling path traversal. Use `Path.Join` (.NET Core +) validate the resolved path under
the intended directory.
**Do use `Path.GetTempFileName()`** -- it creates predictable filenames throws at , files. Use
`Path.GetRandomFileName()` `FileMode.CreateNew` secure, atomic temp creation.
**Do ignore FileSystemWatcher duplicate events** -- editors tools trigger multiple events a single
logical change. Implement debouncing a timer Channel<T> throttle.
**Do rely FileSystemWatcher alone reliable change detection** -- buffer overflows lose events silently.
Handle the `Error` implement periodic rescan a fallback.
**Do assume locking mandatory Linux/macOS** -- `FileStream.Lock` `FileShare` flags use advisory
locking Unix, which non-cooperating processes can bypass. Design protocols accordingly.
**Do only `IOException` ignore `UnauthorizedAccessException`** -- permission errors
`UnauthorizedAccessException`, which does inherit `IOException`. Handle both access error handling.
**Do assume `WriteAsync` reports disk-full errors immediately** -- data may be buffered. Disk-full `IOException`
can surface at `FlushAsync` `Dispose`. Always handle exceptions flush.
---
- [File stream I/O overview](https:
- [.NET I/O improvements](https:
- [RandomAccess API](https:
- [FileSystemWatcher](https:
- [Memory-mapped files](https:
- [
Primary approach: Use Serena symbol operations for efficient code navigation:
serena_find_symbol instead of text searchserena_get_symbols_overview for file organizationserena_find_referencing_symbols for impact analysisserena_replace_symbol_body for clean modificationsWhen to use Serena vs traditional tools:
Example workflow:
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"
# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"