Claude
Skills
Sign in
Back

dotnet-trimming

Included with Lifetime
$97 forever

Making apps/libraries trim-safe. Annotations, ILLink descriptors, IL2xxx warnings, IsTrimmable.

General

What this skill does


# dotnet-trimming

Trim-safe development for .NET 8+ applications and libraries: trimming annotations (`[RequiresUnreferencedCode]`, `[DynamicallyAccessedMembers]`, `[DynamicDependency]`), ILLink descriptor XML for type preservation, `TrimmerSingleWarn` for granular diagnostics, testing trimmed output, fixing IL2xxx/IL3xxx warnings, and library authoring with `IsTrimmable`.

**Version assumptions:** .NET 8.0+ baseline. Trimming shipped in .NET 6, but .NET 8 provides the most complete annotation surface and analyzer coverage. .NET 9 improved warning accuracy and library compat.

**Out of scope:** Native AOT publish pipeline and MSBuild configuration -- see [skill:dotnet-native-aot]. AOT-first design patterns -- see [skill:dotnet-aot-architecture]. WASM AOT compilation -- see [skill:dotnet-aot-wasm]. MAUI-specific AOT and trimming -- see [skill:dotnet-maui-aot]. Source generator authoring -- see [skill:dotnet-csharp-source-generators]. Serialization depth -- see [skill:dotnet-serialization]. Container deployment -- see [skill:dotnet-containers].

Cross-references: [skill:dotnet-native-aot] for AOT compilation pipeline, [skill:dotnet-aot-architecture] for AOT-safe design patterns, [skill:dotnet-serialization] for AOT-safe serialization, [skill:dotnet-csharp-source-generators] for source gen as trimming enabler.

---

## MSBuild Properties: Apps vs Libraries

Apps and libraries use different MSBuild properties for trimming. This distinction is critical -- using the wrong property causes subtle issues.

### For Applications

```xml
<PropertyGroup>
  <!-- Enable trimming on publish -->
  <PublishTrimmed>true</PublishTrimmed>

  <!-- Enable trim analyzer during development -->
  <EnableTrimAnalyzer>true</EnableTrimAnalyzer>

  <!-- Optional: also enable AOT analyzer if targeting AOT -->
  <EnableAotAnalyzer>true</EnableAotAnalyzer>
</PropertyGroup>
```

`PublishTrimmed` tells the linker to remove unreachable code when publishing. `EnableTrimAnalyzer` enables Roslyn analyzers that warn about trim-unsafe patterns during development.

### For Libraries

```xml
<PropertyGroup>
  <!-- Declare the library is trim-safe (auto-enables trim analyzer) -->
  <IsTrimmable>true</IsTrimmable>

  <!-- Declare AOT compatibility (auto-enables AOT analyzer) -->
  <IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
```

**Key difference:** Libraries do not set `PublishTrimmed` -- they are not published as standalone applications. `IsTrimmable` tells consumers that the library's public API is annotated for trimming safety. Setting `IsTrimmable` automatically enables the trim analyzer for the library project.

| Property | Project Type | Effect |
|----------|-------------|--------|
| `PublishTrimmed` | App | Trims on publish, enables linker |
| `EnableTrimAnalyzer` | App | Enables trim warnings during build |
| `IsTrimmable` | Library | Declares trim-safe, auto-enables analyzer |
| `IsAotCompatible` | Library | Declares AOT-safe, auto-enables AOT analyzer |
| `PublishAot` | App | Enables AOT (implies `PublishTrimmed`) |

---

## Trimming Annotations

.NET provides attributes to annotate code that interacts with reflection, helping the trimmer understand what to preserve.

### `[RequiresUnreferencedCode]`

Marks a method as unsafe for trimming. The trimmer and analyzer produce IL2026 warnings when this method is called from trim-safe code.

```csharp
[RequiresUnreferencedCode("Uses reflection to discover plugins")]
public IPlugin LoadPlugin(string typeName)
{
    var type = Type.GetType(typeName)
        ?? throw new InvalidOperationException($"Type {typeName} not found");
    return (IPlugin)Activator.CreateInstance(type)!;
}
```

### `[DynamicallyAccessedMembers]`

Tells the trimmer which members of a type are accessed via reflection, so they are preserved:

```csharp
public T CreateInstance<[DynamicallyAccessedMembers(
    DynamicallyAccessedMemberTypes.PublicConstructors)] T>()
    where T : class
    => (T)Activator.CreateInstance(typeof(T))!;

// The trimmer preserves public constructors of T
// because the constraint tells it what's needed
```

### `[DynamicDependency]`

Explicitly preserves a specific member from trimming:

```csharp
// Preserve a method that is only called via reflection
[DynamicDependency(nameof(OnConfigChanged), typeof(ConfigWatcher))]
public void StartWatching() { /* reflects on OnConfigChanged */ }

// Preserve all public properties (e.g., for serialization)
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties,
    typeof(LegacyDto))]
public void SerializeLegacy(LegacyDto dto) { /* ... */ }
```

### `[UnconditionalSuppressMessage]`

Suppresses a specific trim warning when you have verified the code is safe despite the analyzer's concern:

```csharp
[UnconditionalSuppressMessage("Trimming",
    "IL2026:RequiresUnreferencedCode",
    Justification = "Type is preserved via ILLink descriptor")]
public void CallLegacyCode() { /* ... */ }
```

Use sparingly -- only when you have verified safety through ILLink descriptors or other means.

---

## ILLink Descriptors

ILLink descriptor XML files tell the trimmer to preserve types, methods, or entire assemblies. **Do not use legacy RD.xml** -- it is a .NET Native/UWP format that is silently ignored by modern .NET trimming.

### Descriptor Format

```xml
<!-- ILLink.Descriptors.xml -->
<linker>
  <!-- Preserve specific types -->
  <assembly fullname="MyApp">
    <type fullname="MyApp.Models.PluginConfig" preserve="all" />
    <type fullname="MyApp.Services.LegacyAdapter">
      <method name="Initialize" />
      <method name="ProcessRequest" />
    </type>
  </assembly>

  <!-- Preserve an entire third-party assembly -->
  <assembly fullname="LegacyLibrary" preserve="all" />
</linker>
```

### Registration

```xml
<!-- In .csproj -->
<ItemGroup>
  <TrimmerRootDescriptor Include="ILLink.Descriptors.xml" />
</ItemGroup>
```

### Alternative: TrimmerRootAssembly

For entire assemblies that must not be trimmed:

```xml
<ItemGroup>
  <!-- Preserve entire assembly (no trimming at all) -->
  <TrimmerRootAssembly Include="LegacyLibrary" />
</ItemGroup>
```

---

## TrimmerSingleWarn

By default, the trimmer groups warnings per assembly, showing one summary line. `TrimmerSingleWarn=false` shows every individual warning, which is essential for fixing trim issues.

```bash
# Default: one warning per assembly (hard to debug)
dotnet publish -c Release /p:PublishTrimmed=true
# warning IL2104: Assembly 'MyApp' produced trim warnings

# Detailed: per-occurrence warnings (easier to fix)
dotnet publish -c Release /p:PublishTrimmed=true /p:TrimmerSingleWarn=false
# warning IL2026: MyApp.PluginLoader.LoadPlugin(...) requires unreferenced code
# warning IL2057: Unrecognized value passed to Type.GetType(...)

# Analysis without publishing
dotnet build /p:EnableTrimAnalyzer=true /p:TrimmerSingleWarn=false
```

---

## IL2xxx/IL3xxx Warning Reference

### Trim Warnings (IL2xxx)

| Code | Meaning | Fix |
|------|---------|-----|
| IL2026 | Method has `[RequiresUnreferencedCode]` | Replace with trim-safe alternative or add descriptor |
| IL2046 | Trim attribute mismatch on override | Match annotation from base type |
| IL2057 | Unrecognized `Type.GetType()` argument | Use compile-time known type or `[DynamicDependency]` |
| IL2060 | `MakeGenericType` call with unknown type | Use concrete generic instantiations |
| IL2062 | Value passed to `[DynamicallyAccessedMembers]` parameter has no annotation | Add `[DynamicallyAccessedMembers]` to the source |
| IL2067 | Parameter mismatch for `[DynamicallyAccessedMembers]` | Ensure annotations flow correctly through call chain |
| IL2070 | `this` parameter of `Type.GetProperties()` etc. not annotated | Add `[DynamicallyAccessedMembers]` constraint |
| IL2072 | Return value of a method not annotated | Annotate return type with `[DynamicallyAccessedMembers]` |
| IL2104 | Assembly produced trim warnings (summary) | Use `TrimmerSingleWarn=false` for details |

### AOT Warnings (I
Files: 1
Size: 12.9 KB
Complexity: 15/100
Category: General

Related in General