Assembly Trimming: Remove Unused Code in C# Deployments
Assembly trimming is a .NET deployment optimization that analyzes your application, identifies unused code, and strips it away from your final published binaries. In self-contained C# deployments, where the entire .NET runtime and framework libraries are packaged alongside your application, file sizes can quickly balloon to 60MB or more. By enabling trimming, you can drastically reduce this footprint—often by 50% to 80%—resulting in faster downloads, reduced storage requirements, and quicker startup times.
What Is Assembly Trimming?
During the publish process, the .NET trimmer (an evolved version of
the IL Linker) scans your application starting from the entry point
(Main). It constructs a dependency graph by tracing all
referenced types, methods, fields, and external assemblies. Any code or
library that is not reached during this static analysis is considered
dead code and is excluded from the final output directory.
Enabling Trimming in Self-Contained Deployments
To enable trimming, you must publish your application as a self-contained executable targeting a specific runtime identifier (RID).
1. Configuring the
Project File (.csproj)
You can enable trimming permanently for release builds by adding the
<PublishTrimmed> property inside a
<PropertyGroup> in your .csproj
file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
</Project>2. Using the .NET CLI
Alternatively, you can enable trimming directly from the command line
using the /p:PublishTrimmed=true flag with
dotnet publish:
dotnet publish -c Release -r win-x64 --self-contained true /p:PublishTrimmed=trueReplace win-x64 with your target RID (such as
linux-x64 or osx-arm64).
Trimming Granularity Levels
Modern .NET versions (NET 6 and later) support different trimming
granularities via the <TrimMode> property:
- Partial Trimming
(
<TrimMode>partial</TrimMode>): The default setting. It only trims assemblies that explicitly opt-in via the[assembly: AssemblyMetadata("IsTrimmable", "True")]attribute, which includes most of the core .NET runtime libraries. Application code and third-party libraries not marked as trimmable remain untouched. - Full Trimming
(
<TrimMode>full</TrimMode>): Aggressively analyzes and strips unused code at the member level across all assemblies, including your own application code and external NuGet packages. This yields the smallest possible binary size but requires thorough testing.
Handling Reflection and Dynamic Code
Because trimming relies on static code analysis, dynamic behaviors
like reflection, late-bound dependency injection, and runtime
serialization cannot always be detected. If the trimmer removes a method
that is only called via reflection, your application will throw a
MissingMethodException at runtime.
To prevent critical code from being trimmed:
- Annotate with Attributes: Use code annotations from
the
System.Diagnostics.CodeAnalysisnamespace to inform the trimmer of dynamic dependencies.[DynamicallyAccessedMembers]: Instructs the trimmer to preserve specific types, constructors, or methods.[RequiresUnreferencedCode]: Marks a method as incompatible with trimming, generating build-time warnings when called.
- Exclude Specific Assemblies: If a third-party
dependency is breaking due to trimming, you can prevent the trimmer from
altering it by adding a
<TrimmableAssembly>entry in your.csproj:
<ItemGroup>
<TrimmableAssembly Remove="ProblematicLibrary" />
</ItemGroup>Always test trimmed self-contained builds thoroughly in an environment that mirrors your target production deployment to verify that all runtime-loaded paths function correctly.