DiagnosticCatalog.Trimming
1.0.0
dotnet add package DiagnosticCatalog.Trimming --version 1.0.0
NuGet\Install-Package DiagnosticCatalog.Trimming -Version 1.0.0
<PackageReference Include="DiagnosticCatalog.Trimming" Version="1.0.0" />
<PackageVersion Include="DiagnosticCatalog.Trimming" Version="1.0.0" />
<PackageReference Include="DiagnosticCatalog.Trimming" />
paket add DiagnosticCatalog.Trimming --version 1.0.0
#r "nuget: DiagnosticCatalog.Trimming, 1.0.0"
#:package DiagnosticCatalog.Trimming@1.0.0
#addin nuget:?package=DiagnosticCatalog.Trimming&version=1.0.0
#tool nuget:?package=DiagnosticCatalog.Trimming&version=1.0.0
DiagnosticCatalog.Trimming
π Languages:
π¬π§ English (this file) | π«π· FranΓ§ais
The trimming, Native AOT and single-file warnings (ILxxxx) as strongly referenced constants,
so that UnconditionalSuppressMessageAttribute takes compile-checked references instead of magic
strings.
πͺ Mirrors
Microsoft.NET.ILLink.Tasks 10.0.1077 rules, 3 categories, every identifier and category read from that release's own analyzers. Regenerated 2026-08-05.
Unofficial. Not affiliated with, endorsed by, or supported by Microsoft.
Why this catalogue is not like the others
Every other catalogue in this family exists because nothing reads a suppression's category. Get it wrong and no error, no warning and no failing test will ever tell you.
This one is the opposite case, and it is worse. UnconditionalSuppressMessageAttribute is
parsed β by two different decoders, with two different rules β and an identifier neither of them
accepts is discarded in silence. The linker's decoder is exact about it:
if (!(attribute.ConstructorArguments[1].Value is string warningId)
|| warningId.Length < 6
|| !warningId.StartsWith("IL")
|| !int.TryParse(warningId.AsSpan(2, 4), out info.Id))
Anything that is not IL#### is ignored outright. The compile-time trim analyzer implements its
own rule β truncate at the first colon, then match exactly β so the two do not even agree on what
they accept.
And unlike a mis-categorised [SuppressMessage], the consequence here is not a warning that quietly
stays. A suppression the linker discarded means the warning it was meant to silence was never
silenced, so the pattern it was covering gets trimmed away β and you find out as a
TypeLoadException in production, on a code path nobody exercised before publishing.
Why you have these warnings, and probably did not ask for them
PublishTrimmed and PublishAot are opt-in β except that several SDKs set them for you:
| You are building | Trimming analyzer |
|---|---|
| Blazor WebAssembly | On, every build. Microsoft.NET.Sdk.BlazorWebAssembly sets PublishTrimmed in its own props |
| MAUI on iOS/Android | On, Release |
Anything with PublishAot |
On β AOT implies trimming |
A library declaring IsTrimmable |
On, even though you never publish trimmed yourself |
| An ordinary console, service, or web app | Off, unless you asked |
The switch is not the publish command; it is a project property, so
Microsoft.NET.Sdk.Analyzers.targets turns EnableTrimAnalyzer on at build time. A Blazor
WebAssembly developer sees IL2026 on every dotnet build, having chosen nothing β which is the
same shape as the Roslyn IDE rules under EnforceCodeStyleInBuild.
The two attributes, and which one you need
This is the part worth getting right, because the catalogue serves both and they are not interchangeable.
// Silencing the COMPILE-TIME analyzer β the IL2026 in your build output.
[SuppressMessage(TrimRule.IL2026.Category, TrimRule.IL2026.Id, Justification = "β¦")]
// Silencing the LINKER, which reads the compiled assembly long after the compiler is gone.
[UnconditionalSuppressMessage(TrimRule.IL2026.Category, TrimRule.IL2026.Id, Justification = "β¦")]
SuppressMessageAttribute carries [Conditional("CODE_ANALYSIS")], so it is not preserved in the
compiled assembly. ILLink and ILCompiler run after compilation and read suppressions out of IL β
they cannot see it. That is the entire reason UnconditionalSuppressMessageAttribute exists: same
shape, no [Conditional], so it survives.
Use the unconditional one when the warning must stay silenced through publish. Reach both through this catalogue and the identifier is checked either way.
Installation
<PackageReference Include="DiagnosticCatalog.Trimming" Version="1.0.0" />
That is the only reference you need. This package depends on DiagnosticCatalog, which carries
the DCAT analyzers and code fixes beside its attributes, so referencing this catalogue is what
switches on the checks that validate rule declarations and their use sites β DCAT0009
included, which reports an UnconditionalSuppressMessage whose identifier is not IL####. That
diagnostic shipped before this catalogue did: the check existed, and there were no constants to
feed it.
What is in the package
77 rules across 3 categories, and not one of them carries a help link β the analyzer declares none. There is nothing to click through to, which is exactly where a catalogue earns its keep: the documentation comment on each constant is the only place the rule's own wording is available at the point of use.
| Category | Rules | What they are about |
|---|---|---|
Trimming |
64 | Reflection the trimmer cannot follow β the IL2xxx range |
AOT |
7 | Code that needs runtime code generation, plus the FeatureGuard rules (IL3050, IL4000) |
SingleFile |
6 | Assembly file paths that do not exist in a single-file bundle (IL300x) |
[DiagnosticRule]
public static class IL2026
{
public const string Id = nameof(IL2026);
public const string Category = TrimCategory.Trimming;
}
Categories declared once
TrimCategory holds each category once, and the rules reference it β so a category's spelling
exists in exactly one place. It is internal by design: a suppression reaches a category through
the rule that carries it, TrimRule.IL2026.Category, and never through the category constant on its
own. The two fold to the same string today and stop agreeing the day a rule moves
(ADR-0026).
How it is produced
Not transcribed from documentation. The generator reads the analyzer assemblies' metadata for the
types they mark with [DiagnosticAnalyzer], constructs those, and reads the DiagnosticDescriptor
instances they actually declare β the only source that cannot have drifted. The analyzer ships
inside Microsoft.NET.ILLink.Tasks, the same package the SDK restores when you publish trimmed.
dotnet run --project src/DiagnosticCatalog.Cli -- generate \
--package Microsoft.NET.ILLink.Tasks --package-version latest \
--namespace DiagnosticCatalog.Trimming --container TrimRule \
--output src/DiagnosticCatalog.Trimming/TrimRules.g.cs
How it stays current
A nightly workflow regenerates every catalogue from its upstream package and opens a pull request when anything the catalogue publishes has moved. It never publishes: a category or an id that changed upstream changes a published contract, and a wrong value merged unreviewed would produce no symptom anywhere. A human reads the diff.
A rule retired upstream is never deleted. It is kept and marked [Obsolete] naming the version
that dropped it, so a project still referencing it gets a CS0618 warning telling it to remove the
suppression β rather than a hard error from a member that vanished. Consumers inline constant values
at their own compile time, so deleting one breaks their recompilation.
How it reaches nuget.org
This catalogue rides the trimming release train
and versions independently of the foundation, so it can follow the SDK's ILLink releases without
dragging anything else along.
Publishing is not part of the nightly. A maintainer pushes a trimming-vX.Y.Z tag, and the release
workflow packs the package, embeds an SPDX SBOM, and publishes through NuGet
Trusted Publishing with
signed build provenance β no long-lived API key exists anywhere to leak.
Limits
[SuppressMessage] cannot suppress compiler warnings β CS0219 and friends need
#pragma warning disable, which takes bare identifiers and so can never reference a constant. This
package covers the ILxxxx analyzer rules only.
See also
Every catalogue this repository publishes is listed in one place β pick the one that matches an analyzer you run:
Want a catalogue of your own? Your analyzer's rules, or an internal ruleset, are declared exactly
the way these are: a static class of constants marked [DiagnosticRule], referenced by consumers
instead of retyped. That marker ships in
DiagnosticCatalog, the foundation this catalogue is built
on, and its README is the guide.
Documentation
For using a catalogue, in the order the work happens:
- Getting started β ten minutes: reference this package, rewrite one suppression, break it on purpose and watch the compiler catch it.
- Writing suppressions that the compiler checks β the full version, including migrating the literals you already have.
- Adopting a catalogue on an existing codebase β the severity ramp, Fix all occurrences, scoping by folder, and what order to convert in.
- Configuration
β every severity key, the category-wide switch, and the
PrivateAssetsmistake that silences everything. - Troubleshooting
β by symptom: nothing is reported,
CS0117,CS0618after an upgrade.
The documentation map picks a page by what you are trying to do; every guide exists in English and French. The specification is the normative version of all of it β Β§9.1 is the one about this attribute.
License
Apache-2.0. The rule identifiers, categories and titles are read from a Microsoft analyzer, which is itself MIT-licensed.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- DiagnosticCatalog (>= 1.0.1)
-
net10.0
- DiagnosticCatalog (>= 1.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 890 | 8/7/2026 |