dotnet-performance-patterns
Optimizing .NET allocations/throughput. Span, ArrayPool, ref struct, sealed, stackalloc.
What this skill does
# dotnet-performance-patterns
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+.
**Out of scope:** C# language syntax for Span, records, pattern matching, and collection expressions -- see [skill:dotnet-csharp-modern-patterns]. Coding standards and naming conventions (including sealed class style guidance) -- see [skill:dotnet-csharp-coding-standards]. Microbenchmarking setup and measurement is owned by this epic's companion skill -- see [skill:dotnet-benchmarkdotnet]. Native AOT compilation pipeline and trimming -- see [skill:dotnet-native-aot]. Serialization format performance tradeoffs -- see [skill:dotnet-serialization]. Architecture patterns (caching, resilience, DI) -- see [skill:dotnet-architecture-patterns]. EF Core query optimization -- see [skill:dotnet-efcore-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
```csharp
// BAD: Substring allocates a new string on each call
public static (string Key, string Value) ParseHeader_Allocating(string header)
{
var colonIndex = header.IndexOf(':');
return (header.Substring(0, colonIndex), header.Substring(colonIndex + 1).Trim());
}
// GOOD: ReadOnlySpan<char> slicing avoids all allocations
public static (ReadOnlySpan<char> Key, ReadOnlySpan<char> Value) ParseHeader_ZeroAlloc(
ReadOnlySpan<char> header)
{
var colonIndex = header.IndexOf(':');
return (header[..colonIndex], header[(colonIndex + 1)..].Trim());
}
```
Performance impact: for high-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 in async methods or stored on the heap (it is a ref struct). 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
public async Task<int> ReadAndProcessAsync(Stream stream, Memory<byte> buffer)
{
var bytesRead = await stream.ReadAsync(buffer);
var data = buffer[..bytesRead]; // Memory<T> slicing -- no allocation
return ProcessData(data.Span); // .Span for synchronous processing
}
private int ProcessData(ReadOnlySpan<byte> data)
{
var sum = 0;
foreach (var b in data)
sum += b;
return sum;
}
```
---
## 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;
public int ProcessLargeData(Stream source)
{
var buffer = ArrayPool<byte>.Shared.Rent(minimumLength: 81920);
try
{
var bytesRead = source.Read(buffer, 0, buffer.Length);
// IMPORTANT: Rent may return a larger buffer than requested.
// Always use bytesRead or the requested length, never buffer.Length.
return ProcessChunk(buffer.AsSpan(0, bytesRead));
}
finally
{
ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
// clearArray: true zeroes the buffer -- use when buffer held sensitive data
}
}
```
### Common Mistakes
| Mistake | Impact | Fix |
|---------|--------|-----|
| Using `buffer.Length` instead of requested size | Processes uninitialized bytes beyond actual data | Track requested/actual size separately |
| Forgetting to return the buffer | Pool exhaustion, falls back to allocation | Use try/finally or a `using` wrapper |
| Returning a buffer twice | Corrupts pool state | Null out the reference after return |
| Not clearing sensitive data | Security leak from pooled buffers | Pass `clearArray: true` to `Return` |
---
## readonly struct, ref struct, and in Parameters
### readonly struct -- Defensive Copy Elimination
The JIT must defensively copy non-readonly structs when accessed via `in`, `readonly` fields, or `readonly` methods to prevent mutation. Marking a struct `readonly` guarantees immutability, eliminating these copies:
```csharp
// GOOD: readonly eliminates defensive copies on every access
public readonly struct Point3D
{
public double X { get; }
public double Y { get; }
public double Z { get; }
public Point3D(double x, double y, double z) => (X, Y, Z) = (x, y, z);
// readonly struct: JIT knows this cannot mutate, no defensive copy needed
public double DistanceTo(in Point3D other)
{
var dx = X - other.X;
var dy = Y - other.Y;
var dz = Z - other.Z;
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
}
```
Without `readonly`, calling a method on a struct through an `in` parameter forces the JIT to copy the entire struct to protect against mutation. For large structs in tight loops, this eliminates significant overhead.
### ref struct -- Stack-Only Types
`ref struct` types are constrained to the stack. They cannot be boxed, stored in fields, or used in async methods. This enables safe wrapping of Span\<T\>:
```csharp
public ref struct SpanLineEnumerator
{
private ReadOnlySpan<char> _remaining;
public SpanLineEnumerator(ReadOnlySpan<char> text) => _remaining = text;
public ReadOnlySpan<char> Current { get; private set; }
public bool MoveNext()
{
if (_remaining.IsEmpty)
return false;
var newlineIndex = _remaining.IndexOf('\n');
if (newlineIndex == -1)
{
Current = _remaining;
_remaining = default;
}
else
{
Current = _remaining[..newlineIndex];
_remaining = _remaining[(newlineIndex + 1)..];
}
return true;
}
}
```
### in Parameters -- Pass-by-Reference Without Mutation
Use `in` for large readonly structs passed to methods. The `in` modifier passes by reference (avoids copying) and prevents mutation:
```csharp
// in parameter: pass by reference, no copy, no mutation allowed
public static double CalculateDistance(in Point3D a, in Point3D b)
=> a.DistanceTo(in b);
```
**When to use `in`:**
| Struct Size | Recommendation |
|-------------|---------------|
| <= 16 bytes | Pass by value (register-friendly, no indirection overhead) |
| > 16 bytes | Use `in` to avoid copy overhead |
| Any size, readonly struct | `in` is safe (no defensive copies) |
| Any size, non-readonly struct | Avoid `in` (defensive copies negate the benefit) |
---
## Sealed ClaRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.