mfogliatto / mfogliatto/ReferenceCop
[Performance] ReferenceEvaluationContextFactory.Create uses linear LINQ Contains on NoWarn codes
- Dominant language
- C#
- Stars
- 1
- Forks
- 2
- PR merge metrics
- No merged PRs in 30d
Description
## Description
`ReferenceEvaluationContextFactory.Create()` checks for warning suppression using `noWarnCodes.Contains(Violation.ViolationSeverityWarningCode)`, where `noWarnCodes` is `IEnumerable`. This performs O(n) linear enumeration via LINQ each time.
In the Roslyn analyzer path (`ReferenceCopAnalyzer.AnalyzeCompilation`), `noWarnCodes` is the `IEnumerable` from `NoWarnAssembliesProvider.GetNoWarnByAssembly()`, which returns arrays from `.Split().Select().ToArray()`. The `Contains` call re-enumerates this array each time.
In the MSBuild path (`ReferenceCopTask.Execute`), `noWarnCodes` comes from `ProjectReferenceInfo.NoWarn`, which is similarly an enumerable from `.Split().Select()`.
## Affected Files
- `src/ReferenceCop/Detectors/ReferenceEvaluationContextFactory.cs` — `Create()`, line 22 (`.Contains()` call)
- `src/ReferenceCop.Roslyn/Providers/NoWarnAssembliesProvider.cs` — produces `IEnumerable` values
- `src/ReferenceCop.MSBuild/Providers/MSBuildProjectMetadataProvider.cs` — produces `IEnumerable` from split
## Impact
- **Per-reference cost**: `Contains` on `IEnumerable` is O(n) per call. While individual NoWarn lists are typically small (1-5 codes), this runs for every reference in the compilation. With 100+ references, the overhead accumulates.
- **Unnecessary enumeration**: The LINQ `Contains` extension method enumerates the sequence each time instead of using a set-based lookup.
- **Multiple scans**: If additional suppression codes are added in the future (e.g., checking both `RC0001` and `RC0002`), each check re-enumerates.
## Suggested Optimization
Use a `HashSet` for the NoWarn codes, either at the provider level or at the factory level:
**Option 1 — At the provider level** (preferred, change once):
```csharp
// In NoWarnAssembliesProvider:
public Dictionary> GetNoWarnByAssembly(string noWarnAssembliesString)
{
// ...
var noWarnCodes = new HashSet(
noWarnCodesString.Split(NoWarnCodesSeparator, StringSplitOptions.RemoveEmptyEntries)
.Select(code => code.Trim()),
StringComparer.OrdinalIgnoreCase);
result[assemblyName] = noWarnCodes;
}
```
**Option 2 — Pre-compute the boolean** (simplest):
```csharp
public static ReferenceEvaluationContext Create(T reference, IEnumerable noWarnCodes = null)
{
bool isSuppressed = noWarnCodes?.Any(c =>
c == Violation.ViolationSeverityWarningCode) ?? false;
return new ReferenceEvaluationContext(reference, isSuppressed);
}
```
Since there are currently only two violation codes (`RC0001`, `RC0002`), and only `RC0002` is checked, the boolean pre-compute is the simplest and most effective fix.
Contributor guide
Research direction
Start in src/ReferenceCop/Detectors/ReferenceEvaluationContextFactory.cs at Create(), then inspect the NoWarn values produced by NoWarnAssembliesProvider.cs and MSBuildProjectMetadataProvider.cs. Apply one of the suggested lookup optimizations and verify that suppression behavior remains correct for both the Roslyn analyzer and MSBuild paths.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- build-system, tooling
- Issue type
- Refactor
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100