Contact Us

Making a tiny .NET Core 3.0 entirely self-contained single executable

Mobile App | May 7, 2021

I’ve always been fascinated by making apps as small as possible, especially in the .NET space. No need to ship any files – or methods – that you don’t need, right? I’ve blogged about optimizations you can make in your Dockerfiles to make your .NET containerized apps small, as well as using the ILLInk.Tasks linker from Mono to “tree trim” your apps to be as small as they can be.

Work is on going, but with .NET Core 3.0 preview 6, ILLink.Tasks is no longer supported and instead the Tree Trimming feature is built into .NET Core directly.

Here is a .NET Core 3.0 Hello World app.

Now I’ll open the csproj and add PublishTrimmed = true.

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
</Project>

And I will compile and publish it for Win-x64, my chosen target.

dotnet publish -r win-x64 -c release

Now it’s just 64 files and 28 megs!

If your app uses reflection you can let the Tree Trimmer know by telling the project system about your Assembly, or even specific Types or Methods you don’t want trimmed away.

<ItemGroup>
<TrimmerRootAssembly Include="System.IO.FileSystem" />
</ItemGroup>

The intent in the future is to have .NET be able to create a single small executable that includes everything you need. In my case I’d get “supersmallapp.exe” with no dependencies. Until then, there’s a cool global utility called Warp.

This utility, combined with the .NET Core 3.0 SDK’s now-built-in Tree Trimmer creates a 13 meg single executable that includes everything it needs to run.

C:UsersscottDesktopSuperSmallApp>dotnet warp
Running Publish...
Running Pack...
Saved binary to "SuperSmallApp.exe"

And the result is just a 13 meg single EXE ready to go on Windows.

If you want, you can combine this “PublishedTrimmed” object with “PublishReadyToRun” as well and get a small AND fast app.

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<PublishTrimmed>true</PublishTrimmed>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
</Project>

These are not just IL (Intermediate Language) assemblies that are JITted (Just in time compiled) on the target machine. These are more “pre-chewed” AOT (Ahead of Time) compiled assemblies with as much native code as possible to speed up your app’s startup time. From the blog post:

In terms of compatibility, ReadyToRun images are similar to IL assemblies, with some key differences.

  • R2R assemblies contain IL and native code. They are compiled for a specific minimum .NET Core runtime version and runtime environment (RID). For example, a netstandard2.0 assembly might be R2R compiled for .NET Core 3.0 and Linux x64. It will only be usable in that or a compatible configuration (like .NET Core 3.1 or .NET Core 5.0, on Linux x64), because it contains native code that is only usable in that runtime environment.

I’ll keep exploring .NET Core 3.0, and you can install the SDK here in minutes. It won’t mess up any of your existing stuff.


Sponsor: Suffering from a lack of clarity around software bugs? Give your customers the experience they deserve and expect with error monitoring from Raygun.com. Installs in minutes, try it today!


© 2018 Scott Hanselman. All rights reserved.

This content was originally published here.