Analyzer rule
Spec: LC033 - Use FrozenSet for Provably Read-Only Static Membership Caches
EF Core LINQ performance analyzer and Roslyn analyzer for catching query issues at compile time.
Spec: LC033 - Use FrozenSet for Provably Read-Only Static Membership Caches
Goal
Detect private static readonly HashSet<T> membership caches that can be safely converted to FrozenSet<T> on .NET 8+.
The Problem
HashSet<T> is mutable and optimized for general-purpose set operations. When a cache is initialized once, never mutated, and only used for Contains(...), FrozenSet<T> is a better fit: it trades construction cost for faster steady-state lookups and lower ongoing overhead.
Example Violation
private static readonly HashSet<string> ElevatedRoles = new(StringComparer.OrdinalIgnoreCase)
{
"admin",
"ops"
};
static bool IsElevated(string role) => ElevatedRoles.Contains(role);
The Fix
Convert the cache to FrozenSet<T> and build it once with ToFrozenSet(...).
private static readonly FrozenSet<string> ElevatedRoles = new string[]
{
"admin",
"ops"
}.ToFrozenSet(StringComparer.OrdinalIgnoreCase);
Analyzer Logic
ID: LC033
Category: Performance
Severity: Info
Algorithm
- Require a source-declared
private static readonly HashSet<T>field with a single declarator and an inline initializer. - Require
System.Collections.Frozen.FrozenSet<T>andToFrozenSet(...)to be available in the compilation. - Accept only fixer-safe initializer shapes:
- collection initializer forms (
new HashSet<T>() { ... }, optionally with a comparer), new HashSet<T>(source[, comparer]),source.ToHashSet([comparer]).
- collection initializer forms (
- Track every source reference to the field across the compilation and require every usage to be a direct
Contains(...)call. - Skip any field used in
IQueryable/ expression-tree contexts, passed around through aliases, mutated, enumerated, or touched through any non-Containsmember.
Notes
This rule is intentionally narrow. If there is any ambiguity about initialization, mutability, or usage shape, it stays silent instead of suggesting a speculative rewrite.