subagent-driven-development
OBobra/superpowers298k2,631 new stars in the last 7 days
Guidelines for making .NET libraries and applications trimming-safe and Native AOT compatible. Covers the trimming/AOT model, the MSBuild properties that enable analysis (IsTrimmable, IsAotCompatible, PublishTrimmed, PublishAot), the trimming attributes (RequiresUnreferencedCode, RequiresDynamicCode, DynamicallyAccessedMembers, UnconditionalSuppressMessage), IL2xxx/IL3xxx warning codes, and a pattern playbook: source generators and UnsafeAccessor, generated-interception diagnostic suppressors, intentional runtime scanning boundaries, trimming-safe islands and feature switches, migration analyzers, and temporary warning-approval baselines. Use when introducing trimming/AOT support, resolving IL2xxx/IL3xxx warnings, or making reflection-heavy code trimming-safe in C# / .NET codebases. For System.Text.Json AOT scenarios, see also the serialization skill.
PublishTrimmed) or Native AOT (PublishAot) support to a library or applicationIL2xxx (trimming) and IL3xxx (AOT/single-file) warningsReflection.Emit with compile-time alternativesPublishTrimmed) runs ILLink reachability analysis: only statically reachable code is kept. Reflection the analyzer cannot see is trimmed away and produces warnings.PublishAot) makes trimming mandatory and removes the JIT. Reflection.Emit is unsupported, constructing unknown generic instantiations at runtime is not guaranteed, Expression.Compile() may fall back to interpretation, and reflection works only over members the linker preserved.IL2xxx — trimming warnings: RequiresUnreferencedCode propagation + DynamicallyAccessedMembers dataflow.IL3xxx — AOT and single-file warnings: RequiresDynamicCode / RequiresAssemblyFiles.A library can be trimmable but not AOT-compatible (it uses Reflection.Emit, which trimming tolerates but AOT does not).
typeof(T) in a generic method.Reflection.Emit is unsupported and constructing unknown generic instantiations is not guaranteed.Both are solved the same way: make the required members statically visible to the analyzer, or remove the need for reflection entirely.
Reflection is a legitimate technique and is completely fine in ordinary JIT applications — you need none of this there. The trimming attributes exist to allow reflection under trimming/AOT, not to forbid it: annotate what reflection needs, and the linker keeps it. Reach for source generators, [UnsafeAccessor], or capability gating only when you want a specific reflective surface removed or contained (for example, a public registration API you do not want to ship with [RequiresUnreferencedCode]).
For a library, declare compatibility so the analyzers surface warnings and consumers know:
<PropertyGroup>
<!-- Trim-compatible: enables trim warnings. -->
<IsTrimmable>true</IsTrimmable>
<!-- AOT-compatible: implies IsTrimmable + EnableTrimAnalyzer + EnableSingleFileAnalyzer + EnableAotAnalyzer. -->
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
</PropertyGroup>
For an application, publish trimmed or AOT:
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<!-- or -->
<PublishAot>true</PublishAot>
</PropertyGroup>
Verify by actually publishing — a trimmed/AOT app must produce zero warnings:
dotnet publish -c Release -r <rid> -p:PublishTrimmed=true
dotnet publish -c Release -r <rid> -p:PublishAot=true
A test app that roots the library is the standard way to validate a library:
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
<TrimmerRootAssembly Include="MyLibrary" />
</ItemGroup>
See aot-trimming-playbook-reference.md for the full property table, feature switches, and the warning-approval baseline pattern.
These live in System.Diagnostics.CodeAnalysis. See trimming-attributes-reference.md for the full catalog with exact signatures, warning codes, and rules.
[RequiresUnreferencedCode(message)] — marks code that needs members the linker cannot see. Suppresses warnings inside; emits IL2026 at every call site.[RequiresDynamicCode(message)] — marks code that needs a JIT (emitting, dynamic). Emits IL3050 at call sites.[DynamicallyAccessedMembers(memberTypes)] — declares which members of a Type/string must be preserved. Flows backward from the reflection site to the Type source. Scope it (and the other attributes) to a single accessor by placing the attribute directly on the accessor, or with the [method:], [field:], or [return:] targets.[UnconditionalSuppressMessage(category, checkId, Justification = "...")] — last-resort, IL-persisted suppression; must carry a justification and a real invariant.[DynamicDependency("Member", typeof(T))] — keeps named members but does not silence warnings.Choose the narrowest DynamicallyAccessedMemberTypes for the reflection you actually perform:
| Reflection | Member type |
|---|---|
Activator.CreateInstance<T>(), new T(), Activator.CreateInstance(type) | PublicParameterlessConstructor |
DI activation (ActivatorUtilities.CreateFactory<T>) | PublicConstructors |
| Explicit non-public activation | NonPublicConstructors |
type.GetInterfaces() | Interfaces |
type.GetMethods() | PublicMethods (add NonPublicMethods when needed) |
type.GetProperties() | PublicProperties |
type.GetFields() | PublicFields |
type.GetNestedTypes() | PublicNestedTypes |
First decide whether the code must be trimming/AOT safe at all. A plain JIT app or library needs none of this — reflection is fine there. Only when you or a consumer publish trimmed/AOT does reflection need to become visible to the linker.
The goal is never "remove all reflection"; it is make the reflection statically visible so the linker keeps what it needs. Choose per call site:
Keep the reflection and annotate it — the common case. Put [DynamicallyAccessedMembers(...)] on the Type/string source and propagate it through the chain; mark entry points [RequiresUnreferencedCode]/[RequiresDynamicCode]. The attributes exist precisely to let reflection work under trimming. See trimming-attributes-reference.md.
Replace the reflection with a compile-time alternative — when you want a reflective surface gone (for example, a public registration API you do not want to ship with [RequiresUnreferencedCode]). A source generator emits direct type references (marker attribute → generated registration); [UnsafeAccessor] replaces FieldInfo/MethodInfo/ConstructorInfo. See aot-trimming-playbook-reference.md and aot-trimming-playbook-reference.md.
Isolate a whole dynamic path — when a path is inherently dynamic (emitting proxies, loading plugins by name), move it behind a boundary and provide a trimming-safe alternative selected by RuntimeFeature.IsDynamicCodeSupported. See aot-trimming-playbook-reference.md.
Suppress only as a last resort — a single [UnconditionalSuppressMessage(...)] at an invariant-proven leaf, with a Justification. Never #pragma (not persisted in IL).
See aot-trimming-playbook-reference.md for the full playbook, including object-overload → generic-overload redirects, generated-interception suppressors, intentional scanning boundaries, migration analyzers, strict-mode precedence, and temporary warning-approval baselines.
RequiresUnreferencedCode, RequiresDynamicCode, DynamicallyAccessedMembers (full DynamicallyAccessedMemberTypes list), UnconditionalSuppressMessage, DynamicDependency — each with intent, exact signature, and rules, plus the IL2xxx/IL3xxx warning-code table.[OverloadResolutionPriority] and opt-in analyzers, migration analyzers, warning-approval baselines, known gotchas, and the full generation checklist.IsTrimmable/IsAotCompatible on libraries; PublishTrimmed/PublishAot on apps; zero warnings on publish.DynamicallyAccessedMembers/RequiresUnreferencedCode/RequiresDynamicCode); replace it with source generators, generic (typeof(T)) APIs, or [UnsafeAccessor] only where you want the reflective surface gone.DynamicallyAccessedMembers on every Type/string source; propagate through the chain; [RequiresUnreferencedCode]/[RequiresDynamicCode] on unsafe entry points.RuntimeFeature.IsDynamicCodeSupported, and separate trimming paths with #if or annotations.[UnconditionalSuppressMessage] + justification at invariant-proven leaves; never #pragma/SuppressMessage.OBobra/superpowers298k2,631 new stars in the last 7 days
MAmattpocock/skills288k10,178 new stars in the last 7 days
MAmattpocock/skills288k10,178 new stars in the last 7 days
MAmattpocock/skills288k10,178 new stars in the last 7 days
AFaffaan-m/ECC278k4,046 new stars in the last 7 days
AFaffaan-m/ECC278k4,046 new stars in the last 7 days