Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Performance-oriented architecture patterns for .NET applications. Covers zero-allocation coding with Span<T> and
Memory<T>, buffer pooling with ArrayPool<T>, struct design for performance (readonly struct, ref struct, in
parameters), sealed class devirtualization by the JIT, stack-based allocation with stackalloc, and string handling
performance. Focuses on the why (performance rationale and measurement) rather than the how (language syntax).
Version assumptions: .NET 8.0+ baseline. Span<T> and Memory<T> are available from .NET Core 2.1+ but this skill
targets modern usage patterns on .NET 8+.
Scope
Zero-allocation coding with Span and Memory
Buffer pooling with ArrayPool
Struct design for performance (readonly struct, ref struct, in parameters)
Sealed class devirtualization by the JIT
Stack-based allocation with stackalloc
String handling performance patterns
Out of scope
C# language syntax for Span, records, pattern matching -- see [skill:dotnet-csharp-modern-patterns]
Coding standards and naming conventions -- see [skill:dotnet-csharp-coding-standards]
Microbenchmarking setup and measurement -- see [skill:dotnet-benchmarkdotnet]
Native AOT compilation and trimming -- see [skill:dotnet-native-aot]
Serialization format performance -- see [skill:dotnet-serialization]
Architecture patterns (caching, resilience, DI) -- see [skill:dotnet-architecture-patterns]
Cross-references: [skill:dotnet-benchmarkdotnet] for measuring the impact of these patterns,
[skill:dotnet-csharp-modern-patterns] for Span/Memory syntax foundation, [skill:dotnet-csharp-coding-standards] for
sealed class style conventions, [skill:dotnet-native-aot] for AOT performance characteristics and trimming impact on
pattern choices, [skill:dotnet-serialization] for serialization performance context.
Span<T> and Memory<T> for Zero-Allocation Scenarios
Why Span<T> Matters for Performance
Span<T> provides a safe, bounds-checked view over contiguous memory without allocating. It enables slicing arrays,
strings, and stack memory without copying. For syntax details see [skill:dotnet-csharp-modern-patterns]; this section
focuses on performance rationale.
Zero-Allocation String Processing
// BAD: Substring allocates a new string on each callpublicstatic ( Key, Value) ()
{
colonIndex = header.IndexOf();
(header.Substring(, colonIndex), header.Substring(colonIndex + ).Trim());
}
{
colonIndex = header.IndexOf();
(header[..colonIndex], header[(colonIndex + )..].Trim());
}
```text
Performance impact: high-
{
bytesRead = stream.ReadAsync(buffer);
data = buffer[..bytesRead];
ProcessData(data.Span);
}
{
sum = ;
( b data)
sum += b;
sum;
}
```text
---
;
{
buffer = ArrayPool<>.Shared.Rent(minimumLength: );
{
bytesRead = source.Read(buffer, , buffer.Length);
ProcessChunk(buffer.AsSpan(, bytesRead));
}
{
ArrayPool<>.Shared.Return(buffer, clearArray: );
}
}
```text
| Mistake | Impact | Fix |
|---------|--------|-----|
| Using `buffer.Length` instead of requested size | Processes uninitialized bytes beyond actual data | Track requested/actual size separately |
| Forgetting to the buffer | Pool exhaustion, falls back to allocation | Use / a `` wrapper |
| Returning a buffer twice | Corrupts pool state | Null the reference after |
| Not clearing sensitive data | Security leak pooled buffers | Pass `clearArray: ` to `Return` |
---
The JIT must defensively copy non- structs accessed via ``, `` fields, `` methods to prevent mutation. Marking a `` guarantees immutability, eliminating these copies:
```csharp
Point3D
{
X { ; }
Y { ; }
Z { ; }
=> (X, Y, Z) = (x, y, z);
{
dx = X - other.X;
dy = Y - other.Y;
dz = Z - other.Z;
Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
}
```text
Without ``, calling a method a through an `` parameter forces the JIT to copy the entire to protect against mutation. For large structs tight loops, eliminates significant overhead.
` ` types are constrained to the stack. They cannot be boxed, stored fields, used methods. This enables safe wrapping of Span\<T\>:
```csharp
SpanLineEnumerator
{
ReadOnlySpan<> _remaining;
=> _remaining = text;
ReadOnlySpan<> Current { ; ; }
{
(_remaining.IsEmpty)
;
newlineIndex = _remaining.IndexOf();
(newlineIndex == )
{
Current = _remaining;
_remaining = ;
}
{
Current = _remaining[..newlineIndex];
_remaining = _remaining[(newlineIndex + )..];
}
;
}
}
```text
Use `` large structs passed to methods. The ``
=> a.DistanceTo( b);
```csharp
**When to use ``:**
| Struct Size | Recommendation |
|-------------|---------------|
| <= bytes |
{
=> x * ;
}
:
{
=> x * ;
}
{ ; }
```text
Verify devirtualization `[DisassemblyDiagnoser]` [skill:dotnet-benchmarkdotnet]. See [skill:dotnet-csharp-coding-standards] the project convention of defaulting to classes.
Devirtualization + inlining eliminates:
**vtable lookup** -- indirect memory access to find the method pointer
**Call overhead** -- the actual indirect call instruction
**Inlining barrier** -- calls cannot be inlined; methods can
In tight loops hot paths, the cumulative effect measurable. For framework/library types that are designed extension, always prefer ``.
---
`` allocates memory the stack, avoiding GC entirely. Use small, -size buffers hot paths:
```
{
Span<> buffer = [];
guid.TryFormat(buffer, charsWritten, );
(buffer[..charsWritten]);
}
```text
| Guideline | Rationale |
|-----------|-----------|
| ; overflow crashes the process |
| Use constant bounded sizes only | Runtime-variable sizes risk stack overflow malicious/unexpected input |
| Prefer `Span<T>` assignment over raw pointer | Span provides bounds checking; raw pointers |
| Fall back to ArrayPool large/variable sizes | Gracefully handle cases that exceed stack budget |
```
{
stackThreshold = ;
[]? rented = ;
Span<> buffer = input.Length <= stackThreshold
? [stackThreshold]
: (rented = ArrayPool<>.Shared.Rent(input.Length));
{
written = Encoding.UTF8.GetChars(input, buffer);
(buffer[..written]);
}
{
(rented )
ArrayPool<>.Shared.Return(rented);
}
}
```text
This pattern used throughout the .NET runtime libraries the recommended approach methods that handle both small large inputs.
---
Ordinal comparisons are significantly faster than culture-aware comparisons because they avoid Unicode normalization:
```csharp
isMatch = str.Equals(, StringComparison.Ordinal);
containsKey = dict.ContainsKey(key);
isMatchIgnoreCase = str.Equals(, StringComparison.OrdinalIgnoreCase);
isMatchCulture = str.Equals(, StringComparison.CurrentCulture);
```text
**Default guidance:** Use `StringComparison.Ordinal` `StringComparison.OrdinalIgnoreCase` identifiers, dictionary keys, paths, protocol strings. Reserve culture-aware comparison user-visible text sorting display.
The CLR interns compile-time literals automatically. `.Intern()` can reduce memory runtime strings that repeat frequently:
```csharp
normalized = .Intern(headerName.ToLowerInvariant());
```csharp
**Caution:** Interned strings are never garbage collected. Only intern strings a bounded, {a}{b}
string
string
ParseHeader_Allocating
string header
var
':'
return
0
1
// GOOD: ReadOnlySpan<char> slicing avoids all allocations
throughput parsing (HTTP headers, log lines, CSV rows), Span-based parsing eliminates GC pressure entirely. Measure with `[MemoryDiagnoser]` in [skill:dotnet-benchmarkdotnet] -- the `Allocated` column should read `0 B`.
### Memory\<T\> for Async and Storage Scenarios
`Span<T>` cannot be used inasync methods or stored on the heap (it is a refstruct). Use `Memory<T>` when you need to:
- Pass buffers to async I/O methods
- Store a slice reference in a field or collection
- Return a memory region from a method for later consumption
```csharp
publicasync Task<int> ReadAndProcessAsync(Stream stream, Memory<byte> buffer)
var
await
var
// Memory<T> slicing -- no allocation
return
// .Span for synchronous processing
privateintProcessData(ReadOnlySpan<byte> data)
var
0
foreach
var
in
return
## ArrayPool\<T\> for Buffer Pooling
### Why Pool Buffers
Large array allocations (>= 85,000 bytes) go directly to the Large Object Heap (LOH), which is only collected in Gen 2 GC -- expensive and causes pauses. Even smaller arrays add GC pressure in hot paths. `ArrayPool<T>` rents and returns buffers to avoid repeated allocations.
### Usage Pattern
```csharp
using System.Buffers
publicintProcessLargeData(Stream source)
var
byte
81920
try
var
0
// IMPORTANT: Rent may return a larger buffer than requested.
// Always use bytesRead or the requested length, never buffer.Length.
return
0
finally
byte
true
// clearArray: true zeroes the buffer -- use when buffer held sensitive data
### Common Mistakes
return
try
finally
or
using
out
return
from
true
## readonly struct, ref struct, and in Parameters
### readonly struct -- Defensive Copy Elimination
readonly
when
in
readonly
or
readonly
struct
readonly
// GOOD: readonly eliminates defensive copies on every access
public
readonly
struct
public
double
get
public
double
get
public
double
get
publicPoint3D(double x, double y, double z)
// readonly struct: JIT knows this cannot mutate, no defensive copy needed
publicdoubleDistanceTo(in Point3D other)
var
var
var
return
readonly
on
struct
in
struct
in
this
### ref struct -- Stack-Only Types
ref
struct
in
or
in
async
public
ref
struct
private
char
publicSpanLineEnumerator(ReadOnlySpan<char> text)
public
char
get
private
set
publicboolMoveNext()
if
return
false
var
'\n'
if
-1
default
else
1
return
true
### in Parameters -- Pass-by-Reference Without Mutation
in
for
readonly
in
modifier passes byreference (avoids copying) and prevents mutation:
```csharp
// in parameter: pass by reference, no copy, no mutation allowedpublicstaticdoubleCalculateDistance(in Point3D a, in Point3D b)
in
in
16
Pass byvalue (register-friendly, no indirection overhead) |
| > 16 bytes | Use `in` to avoid copy overhead |
| Any size, readonlystruct | `in` issafe (no defensive copies) |
| Any size, non-readonlystruct | Avoid `in` (defensive copies negate the benefit) |
---
## Sealed Class Performance Rationale
### JIT Devirtualization
When a classis `sealed`, the JIT can replace virtual method calls with direct calls (devirtualization) because no subclass overrideis possible. This enables further inlining:
```csharp
// Without sealed: virtual dispatch through vtablepublicclass OpenService : IProcessor
publicvirtualintProcess(int x)
2
// With sealed: JIT devirtualizes + inlines Process call
public
sealed
class
SealedService
IProcessor
publicintProcess(int x)
2
public
interface
IProcessor
intProcess(int x)
with
in
for
sealed
### Performance Impact
1.
2.
3.
virtual
sealed
and
is
not
for
sealed
## stackalloc for Small Stack-Based Allocations
### When to Use stackalloc
stackalloc
on
for
fixed
in
csharp
publicstaticstringFormatGuid(Guid guid)
// 68 bytes on the stack -- well within safe limits
char
stackalloc
char
68
out
var
"D"
return
new
string
### Safety Guidelines
Keep allocations small (< 1 KB typical, < 4 KB absolute maximum) | Stack space islimited (~1 MB defaulton Windows)
or
from
do
not
for
### Hybrid Pattern: stackalloc with ArrayPool Fallback
## String Interning and StringComparison Performance
### String Comparison Performance
// FAST: ordinal comparison (byte-by-byte)
bool
"expected"
bool
// Dictionary<string, T> uses ordinal by default
// FAST: case-insensitive ordinal (no culture overhead)
bool
"expected"
// SLOW: culture-aware comparison (Unicode normalization, linguistic rules)
bool
"expected"
or
for
internal
file
and
for
and
### String Interning
string
string
for
// Intern frequently-repeated runtime strings to share a single instance
var
string
from
known set (HTTP headers, XML element names). Never intern user input or unbounded data.
### Efficient String Building
| Scenario | Recommended Approach | Why |
|----------|---------------------|-----|
| 2-3 concatenations | String interpolation `$"
"` | Compiler optimizes to `string.Concat` |
| Loop concatenation | `StringBuilder` | Avoids quadratic allocation |
| Known fixed parts | `string.Create` | Single allocation, Span-based writing |
| High-throughput formatting | `Span<char>` + `TryFormat` | Zero-allocation formatting |
```csharp
// string.Create for single-allocation building
public static string FormatId(int category, int item)
{
return string.Create(11, (category, item), static (span, state) =>
{
state.category.TryFormat(span, out var catWritten);
span[catWritten] = '-';
state.item.TryFormat(span[(catWritten + 1)..], out _);
});
}
```text
---
## Performance Measurement Checklist
Before applying any optimization pattern, measure first. Premature optimization without data leads to complex code with no measurable benefit.
1. **Identify the hot path** -- use [skill:dotnet-benchmarkdotnet] to establish a baseline
2. **Measure allocations** -- enable `[MemoryDiagnoser]` and check the `Allocated` column
3. **Apply one pattern at a time** -- change one thing, re-measure, compare to baseline
4. **Check AOT impact** -- if targeting Native AOT ([skill:dotnet-native-aot]), verify patterns are trim-safe
5. **Verify with production-like data** -- synthetic benchmarks can miss real-world allocation patterns
6. **Document the tradeoff** -- every optimization trades readability or flexibility for speed; record the measured gain
---
## Agent Gotchas
1. **Measure before optimizing** -- never apply Span/ArrayPool/stackalloc without a benchmark showing the allocation or latency problem. Premature optimization produces unreadable code for no measurable gain.
2. **Do not use stackalloc with variable sizes from untrusted input** -- stack overflow crashes the process with no exception handler. Always validate bounds or use the hybrid stackalloc/ArrayPool pattern.
3. **Always mark value types `readonly struct` when they are immutable** -- without `readonly`, the JIT generates defensive copies on every `in` parameter access and `readonly` field access, silently negating the performance benefit of using structs.
4. **Return rented ArrayPool buffers in finally blocks** -- forgetting to return starves the pool and causes fallback allocations that negate the benefit.
5. **Use `StringComparison.Ordinal` for internal comparisons** -- omitting the comparison parameter defaults to culture-aware comparison, which is slower and produces surprising results for technical strings (file paths, identifiers).
6. **Sealed classes help performance only when the JIT can see the concrete type** -- if the object is accessed through an interface variable in a non-devirtualizable call site, sealing provides no benefit. Verify with `[DisassemblyDiagnoser]`.
7. **Do not re-teach language syntax** -- reference [skill:dotnet-csharp-modern-patterns] for Span/Memory syntax details. This skill focuses on when and why to use these patterns for performance.
---
## Knowledge Sources
Performance patterns in this skill are grounded in guidance from:
- **Stephen Toub** -- .NET Performance blog series ([devblogs.microsoft.com/dotnet/author/toub](https://devblogs.microsoft.com/dotnet/author/toub/)). Authoritative source on Span\<T\>, ValueTask, ArrayPool, async internals, and runtime performance characteristics.
- **Stephen Cleary** -- Async best practices and concurrent collections guidance. Author of *Concurrency in C# Cookbook*.
- **Nick Chapsas** -- Modern .NET performance patterns and benchmarking methodology.
> These sources inform the patterns and rationale presented above. This skill does not claim to represent or speak for any individual.
Code Navigation (Serena MCP)
Primary approach: Use Serena symbol operations for efficient code navigation:
Find definitions: serena_find_symbol instead of text search
Understand structure: serena_get_symbols_overview for file organization
Track references: serena_find_referencing_symbols for impact analysis
Precise edits: serena_replace_symbol_body for clean modifications
When to use Serena vs traditional tools:
Use Serena: Navigation, refactoring, dependency analysis, precise edits
Use Read/Grep: Reading full files, pattern matching, simple text operations
Fallback: If Serena unavailable, traditional tools work fine