Getting Started with ZeroAlloc.Analyzers
What is ZeroAlloc.Analyzers?
ZeroAlloc.Analyzers is a Roslyn analyzer NuGet package that detects allocation-heavy patterns in C# code and suggests zero or low-allocation alternatives. It covers 64 rules across 15 categories — from collection misuse and string concatenation to boxing, LINQ, async, value type pitfalls, cache-line data layout, and Native AOT compatibility. The package is multi-TFM aware: rules are automatically enabled or disabled based on the consuming project's <TargetFramework>, so you only see diagnostics that are actionable for your target runtime.
Installation
Add the package to your project via the .NET CLI:
dotnet add package ZeroAlloc.Analyzers
Or add the PackageReference directly in your .csproj file:
<PackageReference Include="ZeroAlloc.Analyzers" Version="*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
The <PrivateAssets>all</PrivateAssets> setting ensures the analyzer is not propagated as a transitive dependency to consumers of your package — it stays a private development-time tool.
First Use
After installing the package, simply build your project:
dotnet build
Diagnostics are emitted during compilation and appear both in your IDE and in the MSBuild output. For example, you might see output like:
warning ZA0201: Avoid string concatenation in loops. Consider using a StringBuilder or interpolated string handler. [MyProject.csproj]
warning ZA0601: Avoid LINQ methods in hot-path loops. Consider caching the result outside the loop. [MyProject.csproj]
info ZA0107: Collection initialized without a known capacity. Consider pre-sizing with the expected element count. [MyProject.csproj]
Each diagnostic message includes the rule ID, a short description of the problem, and the file and line number where the issue was found. Rules with available code fixes also surface a suggested fix that can be applied directly from the IDE or via dotnet format.
IDE Setup
ZeroAlloc.Analyzers works automatically in all major C# IDEs — no additional configuration is required beyond installing the package.
- Visual Studio 2022 — Diagnostics appear as squiggly underlines in the editor. Rules that have code fixes surface a lightbulb icon with one-click suggestions.
- JetBrains Rider — Diagnostics are surfaced in the editor, the Errors & Warnings tool window, and the Inspection Results panel.
- Visual Studio Code with C# Dev Kit — Diagnostics appear inline in the editor via the language server, and fixes are accessible through the quick-fix menu (
Ctrl+.).
Because the analyzer ships as a standard Roslyn analyzer inside the NuGet package, any editor that supports the .NET language server (OmniSharp, Roslyn LSP) will surface diagnostics without additional setup.
TFM Awareness
Rules that rely on APIs introduced in specific .NET versions are automatically gated on the consuming project's <TargetFramework>. This means you will never see a diagnostic that suggests an API that does not exist in your target runtime.
For example:
- ZA0101 (Use FrozenDictionary) only fires when targeting
net8.0or later, becauseFrozenDictionary<TKey, TValue>was introduced in .NET 8. - ZA0203 (Use AsSpan instead of Substring) only fires on
net5.0+. - ZA0801 (Avoid
Enum.HasFlagboxing) fires only when targeting frameworks belownet7.0, where the JIT does not yet optimize the call.
For multi-targeting projects (<TargetFrameworks> plural), each TFM compilation is analyzed independently with the correct rule set for that target.
See configuration.md for details on how TFM gating works and how to override it.
Quick Suppression
If a diagnostic is not applicable to your codebase, you can suppress it in several ways.
Inline pragma
#pragma warning disable ZA0101
var lookup = new Dictionary<string, int>(data); // intentionally not frozen
#pragma warning restore ZA0101
Per-file or project-wide via .editorconfig
[*.cs]
dotnet_diagnostic.ZA0101.severity = none
To downgrade a warning to a suggestion:
dotnet_diagnostic.ZA0601.severity = suggestion
To promote an info diagnostic to a warning or error:
dotnet_diagnostic.ZA0107.severity = warning
dotnet_diagnostic.ZA0109.severity = error
See configuration.md for the full suppression and severity-tuning guide, including how to suppress categories of rules at once.
Rule Categories
ZeroAlloc.Analyzers organizes its 64 rules into 15 categories:
All Rules
The tables below list all 64 rules grouped by category. Rule IDs link to the corresponding section in each category's reference document. The Min TFM column shows the minimum target framework required for the rule to fire; Any means the rule applies to all supported frameworks.
Collections (ZA01xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA0101 | Use FrozenDictionary for read-only lookups | Info | net8.0 |
| ZA0102 | Use FrozenSet for read-only membership tests | Info | net8.0 |
| ZA0103 | Use CollectionsMarshal.AsSpan for List iteration | Info | net5.0 |
| ZA0104 | Use SearchValues for repeated char/byte lookups | Info | net8.0 |
| ZA0105 | Use TryGetValue instead of ContainsKey + indexer | Warning | Any |
| ZA0106 | Avoid premature ToList/ToArray before LINQ | Warning | Any |
| ZA0107 | Pre-size collections when capacity is known | Info | Any |
| ZA0108 | Avoid redundant ToList/ToArray materialization | Warning | Any |
| ZA0109 | Avoid zero-length array allocation | Warning | Any |
| ZA0110 | Declare params as ReadOnlySpan<T> | Info | net8.0 (C# 13) |
| ZA0111 | Enumerate the ConcurrentDictionary instead of its Keys or Values | Info | Any |
Strings (ZA02xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA0201 | Avoid string concatenation in loops | Warning | Any |
| ZA0202 | Avoid chained string.Replace calls | Info | Any |
| ZA0203 | Use AsSpan instead of Substring | Info | net5.0 |
| ZA0204 | Use string.Create instead of string.Format | Info | net6.0 |
| ZA0205 | Use CompositeFormat for repeated format strings | Info | net8.0 |
| ZA0206 | Avoid span.ToString() before Parse | Info | net6.0 |
| ZA0208 | Avoid string.Join boxing overload | Warning | Any |
| ZA0209 | Avoid value type boxing in string concatenation | Warning | Any |
| ZA0210 | Use a UTF-8 string literal | Warning | Any (C# 11) |
| ZA0211 | Use the span-based Split | Info | net9.0 |
| ZA0212 | Use TryParse instead of catching the exception from Parse | Info | Any |
Memory (ZA03xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA0301 | Use stackalloc for small fixed-size buffers | Info | Any |
| ZA0302 | Use ArrayPool for large temporary arrays | Info | Any |
| ZA0303 | Return rented arrays to the pool | Warning | Any |
| ZA0304 | Use a ReadOnlySpan<T> property for constant lookup tables | Info | Any |
Logging (ZA04xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA0401 | Use LoggerMessage source generator | Info | net6.0 |
Boxing (ZA05xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA0501 | Avoid boxing value types in loops | Warning | Any |
| ZA0502 | Avoid closure allocations in loops | Info | Any |
| ZA0504 | Avoid defensive copies on readonly structs | Info | Any |
LINQ (ZA06xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA0601 | Avoid LINQ methods in loops | Warning | Any |
| ZA0602 | Avoid params calls in loops | Info | Any |
| ZA0603 | Use .Count/.Length instead of LINQ .Count() | Info | Any |
| ZA0604 | Use .Count > 0 instead of LINQ .Any() | Info | Any |
| ZA0605 | Use indexer instead of LINQ .First()/.Last() | Info | Any |
| ZA0606 | Avoid foreach over interface-typed collection | Warning | Any |
| ZA0607 | Avoid multiple enumeration of IEnumerable | Warning | Any |
Regex (ZA07xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA0701 | Use GeneratedRegex for compile-time regex | Info | net7.0 |
Enums (ZA08xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA0801 | Avoid Enum.HasFlag (boxes on pre-net7.0) | Info | <net7.0 |
| ZA0802 | Avoid Enum.ToString() allocations | Info | Any |
| ZA0803 | Cache Enum.GetName/GetValues in loops | Info | Any |
Sealing (ZA09xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA0901 | Consider sealing classes | Info | Any |
Serialization (ZA10xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA1001 | Use JSON source generation | Info | net7.0 |
Async (ZA11xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA1101 | Elide async/await on simple tail calls | Info | Any |
| ZA1102 | Dispose CancellationTokenSource | Info | Any |
| ZA1104 | Avoid Span<T> in async methods | Warning | Any |
Delegates (ZA14xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA1401 | Use static lambda when no capture needed | Info | net5.0 |
| ZA1402 | Use the state-passing overload instead of a capturing lambda | Info | Any |
Value Types (ZA15xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA1501 | Override GetHashCode on struct keys | Info | Any |
| ZA1502 | Avoid finalizers, use IDisposable | Info | Any |
| ZA1503 | Implement IEquatable<T> on structs used as hash keys | Info | Any |
Data Layout (ZA16xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA1601 | Reorder struct fields to reduce padding | Info | Any |
ZA1602(isolate Interlocked-updated fields to avoid false sharing) is disabled by default — see Data Layout rules to opt in.
Native AOT (ZA17xx)
| Rule ID | Title | Severity | Min TFM |
|---|---|---|---|
| ZA1701 | Avoid compiling expression trees at runtime | Info | Any |
| ZA1702 | Avoid runtime IL generation | Info | Any |
| ZA1703 | Avoid constructing generic types or methods at runtime | Info | Any |
| ZA1704 | Avoid reflection-based serializers | Info | Any |
| ZA1706 | Mark libraries as AOT-compatible | Info | net8.0 |
| ZA1707 | Use the configuration-binding source generator | Info | Any (Binder 8.0+, C# 12) |
| ZA1708 | Use a source-generated options validator | Info | Any (Options 8.0+, C# 8) |
| ZA1709 | Avoid dynamic dispatch | Info | Any |
| ZA1710 | Avoid assembly-scanning registration | Info | Any |
ZA1705(avoid resolving types or assemblies by name) is disabled by default — see Native AOT rules to opt in. All ZA17xx rules stand down automatically when the SDK's own AOT analyzer is enabled, except ZA1710, which stands down per call and only for scanning APIs that are trim- or AOT-annotated.
Next Steps
- Configuration guide — Customize severities, suppress rules, and understand TFM gating in depth.
- Contributing — Learn how to add new rules, write tests, and submit pull requests.