Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
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