In .NET Core 3.0, we were introduced to the concept of publishing an application as a single exe file and around that time, I wrote a bit of a guide on it here.
But there was a couple of things in that release that didn’t sit well with people. The main issues were :
- The single file exe was actually a self extracting zip that unzipped to a temp location and then executed. This at times created issues in terms of security or on locked down machines.
- The file size was astronomical (70MB for a Hello World), although I was at pains to say that this includes the entire .NET Core runtime so the target machine didn’t need any pre-requisites to be installed.
Since then, there have been some incremental increases but I want to talk about where we are at today when it comes to Single File Apps in .NET 6.
Single File Publish Basics
I’ve created a console application that does nothing but :
using System; Console.WriteLine("Hello, World!"); Console.ReadLine();
Other than this, it is a completely empty project created in Visual Studio.
To publish this as a single executable, I can open a terminal in my project folder and run :
dotnet publish -p:PublishSingleFile=true -r win-x64 -c Release --self-contained true
I’ll note that when you are publishing a single file you *must* include the target OS type as the exe is bundled specifically for that OS.
If I then open the folder at C:\MyProject\bin\Release\net6.0\win-x64\publish, I should now see a single EXE file ready to go.
Of course this is a pretty dang large file still clocking in at 60MB (Although down slightly from my last attempt which was over 70mb!).
But to show you that this is hardly .NET’s fault, I’m going to run the following :
dotnet publish -p:PublishSingleFile=true -r win-x64 -c Release --self-contained false
Notice that I set –self-contained false to not bundle the entire .NET runtime with my application. This does mean that the runtime needs to be installed on the target machine however. Executing this I can now get down to a puny 150KB.
It’s a trade off for sure. I remember selling C# utilities 15 odd years ago, and it was a bit of a hassle making sure that everyone had the .NET Framework runtime installed. So for me, size isn’t that much of a tradeoff to not have to deal with any runtime problems (Even different versions of the runtime).
I’ll also note that these publish flags can be added to the csproj file instead, but I personally had issues debugging my application with many of them on. And to be fair as well, how I publish an application I may not want to be included in the application itself. But investigate it more if that’s your thing.
Self Extracting Zip No More
As mentioned earlier, a big complaint of the old “Single EXE” type deployments from .NET Core was that it was nothing more than a zip file with everything in it. Still nice but.. Maybe not as magic as first thought. In .NET 6, for the most part, this has been changed to a true single file experience where everything is loaded into memory, rather than extracted into temporary folders.
I’ll note that there are flags for you to use the legacy experience of doing a self extract but.. Why would you?
Much like self contained deployments in .NET Core 3, .NET 6 has the ability to trim unneeded dependencies from your application. By default, when you publish a self contained application you get everything and the kitchen sink. But by using .NET’s “trimming” functionality, you can remove dependencies from the runtime that you aren’t actually using.
This can lead to unintended consequences though as it’s not always possible for the compiler to know which dependencies you are and aren’t using (For example if you are using reflection). The process in .NET 6 is much the same as it has always been so if you want more information, feel free to go back to this previous article to learn more : https://dotnetcoretutorials.com/2019/06/27/the-publishtrimmed-flag-with-il-linker/
That being said, IL Trimming has said to have improved in this latest version of .NET so let’s give it a spin!
dotnet publish -p:PublishSingleFile=true -r win-x64 -c Release --self-contained true -p:PublishTrimmed=true
I want to note that in .NET Core 3, using this flag we went down to about 20MB in size. This time around?
We are now down to 10MB. Again, I want to point out that this is a self contained application with no runtime necessary.
Let’s say that we still want to use self contained deployments, but we are aiming for an even smaller file size. Well starting with .NET 6, we now have the ability to enable compression on our single file apps to squeeze even more disk space.
Let’s just compress the single file app *without* trimming. So we run the following :
dotnet publish -p:PublishSingleFile=true -r win-x64 -c Release --self-contained true -p:EnableCompressionInSingleFile=true
So we were starting with around 60MB file size and we are now at :
A tidy 30MB savings. Really not that bad!
Compressing your single file app does come with a startup cost. Your application may be slower to load so again, a small tradeoff.