In a previous post we looked at how we can publish a single exe from a .NET Core project. Now in it, I talk about how the resulting binary is absolutely massive, 70MB for a simple hello world console app! But I also talked about how there are now ways to significantly chop that down.
Despite that, when shared with Reddit, there was some comments that weren’t so impressed. Most notably this gem :
But that’s cool. For Scellow and anyone else that wasn’t impressed with the size of the exe, let’s look at how we can chop it down.
You Need .NET Core 3.0 (Preview 6+)
Like all these features I’m writing about recently, you’re gonna need the very latest version of .NET Core 3.0. At the time of writing it’s Preview 6. Any version earlier will simply not fly. And again, if that isn’t you and you don’t want to muck around with preview versions, then you’ll just have to wait!
Before we go on, you might hear IL Linker talked about a lot. Possibly because it’s in the title of this post! But that’s because IL Linker is essentially the backbone of this entire feature. However where previous it was this external tool you had to grab from a nuget feed, now it’s built directly into .NET Core 3.0+ which is pretty impressive!
Anyway we previously published our application using the following command :
dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true
And with that, we ended up with an exe over 70MB big :
Not good. But literally all we have to do is add one flag and we can chop it down a bit :
dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true
So… 29MB. And this is about the time where you go “but shouldn’t this be KB in size?”. And honestly the answer is yes. It should be. But we have to remember that this is still a completely self contained application that doesn’t even require the target machine to know what .NET Core is. So even if we aren’t using things like say.. cryptography so we can get rid of that, there is probably a lot of basic and primitive .NET Core features that we just can’t get rid of.
Still. It’s an exe half the size with no functional difference between the two.
Through various forms of reflection, we may end up loading assemblies at runtime that aren’t direct references. Take this (very convoluted) example of loading an assembly at runtime :
static void Main(string args)
Now when debugging this locally, and we have .NET Core installed, we ask for System.Security and it knows what that is because we are using the installed .NET Core platform. So running it, we get :
System.Security, Version=188.8.131.52, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
But if we publish this using the PublishTrimmed flag from the command line, then run it :
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'System.Security, Culture=neutral, PublicKeyToken=null'.
The system cannot find the file specified.
Unsurprisingly, we have trimmed a bit too much off. Because I don’t actually reference System.Security anywhere, it’s decided we aren’t actually using it. But there is a way to specify that we know best, and to package it up for us.
All we need to do is edit our csproj file for our project and add :
<TrimmerRootAssembly Include="System.Security" />
Now we will bundle up System.Security with our single exe and everything will run smooth! Pretty nice I have to say!
You may ask yourself “Well how will I know when I need to do this?”. Frankly 99% of the time you won’t. If you decide to trim your exe, you will need to really give it a good test before letting it out into the wild.