Comparing .netshrink Tools and Techniques for Smaller .NET Builds

Step-by-Step: Using .netshrink to Optimize Your .NET DeploymentOptimizing deployment size for .NET applications reduces delivery time, lowers storage and bandwidth costs, and can improve startup performance (especially for serverless, containerized, or edge scenarios). .netshrink is a tool designed to reduce the size of .NET deployments by trimming unused code, compressing assemblies, and producing smaller distributables while preserving runtime correctness. This guide walks through how to evaluate, configure, and apply .netshrink to real-world .NET projects safely.


What .netshrink does (brief)

  • Trims unused IL and metadata from assemblies so only required code remains.
  • Performs assembly-level and resource-level compression to reduce bytes on disk.
  • Produces optimized deployment bundles for frameworks, self-contained apps, and single-file distributions.
  • Offers tooling to analyze and verify what was removed and where potential breaking changes might occur.

When to use .netshrink

  • You need smaller distribution artifacts for constrained environments (containers, edge devices, mobile, or CDNs).
  • You distribute many identical builds and want to reduce storage/bandwidth costs.
  • You need to improve cold-start times by reducing I/O and JIT work (careful validation required).
  • You have an advanced CI/CD pipeline and can run thorough tests after shrinking.

Do not use aggressive shrinking on critical production builds without thorough integration, runtime, and QA testing first. Trimming can remove code paths used via reflection, dynamic invocation, or runtime-generated types unless properly preserved.


Prerequisites

  • A working .NET development environment (SDK matching your target, e.g., .NET 6, .NET 7, .NET 8).
  • Latest version of the .netshrink tool installed (CLI or MSBuild integration).
  • Full test suite (unit, integration, and, if available, end-to-end tests).
  • Source control and an easy rollback path for builds.

Step 1 — Install and integrate .netshrink

  1. Install via the recommended channel (CLI or NuGet package for MSBuild). For example, if using a CLI installer:
    • dotnet tool install -g netshrink
  2. For MSBuild integration, add the .netshrink MSBuild package to your project or solution:
    • Add a PackageReference in your project file:
      
      <PackageReference Include="NetShrink.MSBuild" Version="x.y.z" PrivateAssets="all" /> 
  3. Confirm installation:
    • netshrink –version
    • Or build with MSBuild and ensure the NetShrink targets run.

Step 2 — Choose a shrinking mode

.netshrink typically offers multiple modes; choose based on your needs:

  • Conservative (safe): minimal trimming, primarily compresses resources and removes unreachable metadata. Lowest risk.
  • Balanced: trims obvious unused code paths and compresses more aggressively. Medium risk.
  • Aggressive: maximum size reduction; may remove code used via reflection unless explicitly preserved. Highest risk.

Start with Conservative or Balanced for production pipelines; use Aggressive only when you can thoroughly test.


Step 3 — Analyze your application (dry run)

Before making changes, run analysis to see what will be removed and what dependencies are uncertain.

  • Run a dry-run analysis:
    • netshrink analyze –project MyApp.csproj –mode balanced –output analysis-report.json
  • Review the report for:
    • Assemblies and types flagged as removable.
    • Reflection/dynamic usage warnings.
    • Resources and native libraries candidates for compression or exclusion.

Look for false positives where your app uses reflection, JSON serializers, dependency injection, ORMs or platform-specific native calls.


Step 4 — Annotate code to preserve required members

If analysis reports show members that are actually needed at runtime (reflection, serializers, dependency injection), add preservation hints:

  • Use attributes (if supported) like [Preserve], [DynamicDependency], or the linker XML description files.
  • Example of DynamicDependency attribute:
    
    [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(MyType))] void EnsureMyTypeMethods() { } 
  • For frameworks like ASP.NET Core, annotate controllers, model types, Razor components, and pages referenced by reflection or routing.

Alternative: supply a linker configuration file that lists assemblies/types/members to keep:

<linker>   <assembly fullname="MyLibrary">     <type fullname="MyLibrary.SpecialType" preserve="all" />   </assembly> </linker> 

Step 5 — Configure .netshrink settings

Tune settings in project file or a separate config file. Typical options:

  • Mode: conservative | balanced | aggressive
  • Preserve reflection usage: true/false (or list)
  • Compression level: none | standard | maximum
  • Single-file packaging: enabled/disabled
  • Native AOT considerations: preserve native entry points

Example MSBuild properties in MyApp.csproj:

<PropertyGroup>   <NetShrinkMode>balanced</NetShrinkMode>   <NetShrinkPreserveReflection>true</NetShrinkPreserveReflection>   <NetShrinkCompression>standard</NetShrinkCompression> </PropertyGroup> 

Step 6 — Build and run tests in CI

  1. Add a pipeline step to produce a shrunk build:
    • dotnet publish -c Release -r linux-x64 /p:NetShrinkMode=balanced
  2. Run the full test suite against the shrunk artifact.
  3. Perform integration tests that exercise reflection-heavy flows, dynamic loading, plugins, serialization, DI, and platform-specific code.
  4. Use synthetic user flows and monitoring to catch runtime errors early.

If tests fail, consult the analysis report for the missing members; add preservation annotations or adjust mode.


Step 7 — Inspect and validate the output

  • Compare sizes before/after:
    • Use du, ls -lh, or your build system artifacts list.
  • Inspect the shrunk assemblies:
    • Use ILSpy/dotPeek or dotnet list package / reflection tools to confirm presence/absence of types.
  • Run memory and startup profiling if startup or cold-start is a priority.

Step 8 — Handle single-file and native AOT builds

  • Single-file publishing packs assemblies into one executable; .netshrink can both shrink and compress contents.
  • For native AOT or trimmed single-file builds, be extra cautious — native entry points, P/Invoke, and runtime code generation often require preserved metadata.
  • Test on target OS/architecture and consider per-architecture shrink settings.

Troubleshooting common issues

  • Missing type/method at runtime: add DynamicDependency or preserve in linker XML.
  • JSON (de)serialization failures: preserve model types and their constructors/properties.
  • Reflection-based DI failures: preserve services and factory methods used via reflection.
  • Third-party libraries failing: configure preserve rules for those assemblies or disable aggressive trimming for them.

Best practices and tips

  • Always run a full test matrix after shrinking (unit, integration, E2E, and smoke tests in staging).
  • Start conservative; iterate toward more aggressive settings as confidence grows.
  • Keep an annotated list of preserve rules in source control and review them during refactors.
  • Use CI gating to ensure only tested shrunk builds reach production.
  • Monitor production for unexpected exceptions after deployment; canary or phased rollouts help limit impact.

Example workflow (summary)

  1. Install .netshrink and integrate into MSBuild/CI.
  2. Run netshrink analyze and review the report.
  3. Add preservation attributes/linker XML for reflection uses.
  4. Configure mode and compression in project file.
  5. Build/publish shrunk artifact in CI and run full tests.
  6. Deploy gradually and monitor.

Conclusion

.netshrink can significantly reduce .NET deployment sizes and improve certain runtime characteristics when used carefully. The key is thorough analysis, conservative starting settings, explicit preservation of reflection-used members, and comprehensive testing. Follow the step-by-step process above to adopt .netshrink safely in your deployment pipeline, and iterate to balance size savings with runtime correctness.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *