.NET 8 continues to improve upon one of the biggest changes to the framework in recent years. The kicker is that a majority of .NET developers are still unaware of this feature and why it could be so useful in the future. This article is going to change that...
As of .NET 7, Microsoft is putting an ever-increasing focus on Ahead of Time compilation (AOT). In case this term is new to you, AOT is a new .NET compile mode.
When you compile your solution using AOT, your end application will be built very differently compared to how your application will have traditionally been generated. Aside from .NET Core, AOT is probably the biggest step away from the origins of .NET since its launch over 20 years ago.
As your end application output is so different using AOT, implementing this new feature could potentially have the biggest improvement on your overall application performance ever.
So like all new technology releases, this new feature sounds amazing, however, as this is still a new ever-growing area within .NET there are also a bunch of cavets that you need to be aware of before you consider using AOT right now.
In this article, we will dig into why AOT changes the game so much, how certain developers can make use of this feature now, and ultimately where this feature is going in the future. If you want a great way to invest 10 minutes of your life, read on
Why is AOT and why does .NET need it?
Before we talk about AOT, let's think about how .NET has always worked since its launch in 2002. Previously, in .NET when you built your code, regardless of the programming language you used (C#. Visual Basic, F#), on build, all the code within your solution would be converted into something called Intermediate Language (IL).
At a high level of thinking, IL code can be thought of as a coding syntax that is hardware-independent. IL is not opinionated or optimized to run on any particular bit of hardware or operating system. Its purpose is to be an intermediary step between say C# and any operating system/hardware combo you decide to host your application on.
Historically, .NET apps could only run on a Windows environment. The reason for this was not a limit of the .NET architecture, but a limit in the way the framework was implemented. .NET Framework 4 and below was written in a way that it could only be installed on a Windows system. Additionally, the core framework code also made these assumptions. For example, .NET used to install itself within the 'Windows` folder. Things like the .NET global cache were also implemented assuming the files lived within this location as well.
This limitation has meant that since .NET was first launched, even though it could support code written using different programming syntaxes, as well as being able to output code to work on any operating system/hardware combo, it couldn't.
Over the years, Microsoft wanted to allow .NET apps to run within things like a docker container, however, as Framework and Windows were so ingrained, a big rewrite of .NET was required to provide this type of support. It was only until .NET Core was released that this benefit was fully utilized and made available to developers.
In .NET Core onwards, before your app can run, the IL code needs to be converted into hardware-supported code. Depending on if you want your code to run on Windows, Linux, or Mac, then the final assembly code that needs to be generated will be different.
This is why .NET uses something called the JIT compiler, to take this neutral IL code, and then when your app first starts at run-time, the JIT compiler converts the IL code into a format that the underlining server hardware can understand.
The benefit of this approach is that you can write your code once and then it can be easily converted and optimized to run on any platform. Well.. I say easy, however, I have no idea how to write this code myself... it's easy as the Microsoft team has done all the hard bits for you ;)
This is how .NET has always worked, AOT changes all that. AOT removes this additional IL step. When you compile your code using AOT, it will generate end assembly code based on a specific target hardware. It removes the need for IL and for the JIT compiler to convert your code at run-time!
As a developer, you might never notice the difference between classic and AOT mode. As long as your code compiles and runs, who cares? This is the reason why I think AOT is a hiddden feature as it's mainly under-the-hood stuff and not a noticeable usable feature.
Now obviously, there must be some good benefits for Microsoft to invest all this time and energy into completely changing things. The way I see it, there are three big benefits of compiling code using AOT:
Quicker Apps: I think this point is hopefully pretty obvious. Code compiled ahead of time and converted natively eliminates the need for JIT compilation during runtime. This means that when your application first starts it should be quicker as .NET has less to do! According to Microsoft research (which can be found here the overall startup time for an app published using AOT can be reduced to around 80%!
Quicker Deployments: In AOT, the generated code is completely targeted to the end hardware. As the code is more optimized, you can ship your app without all the previously required runtime libraries (just like a self-contained app). This obviously reduces the total app size that will need to be deployed to your server, speeding up the process. Additionally, the end code should also be minimized. as things like the startup code are compiled into a single file so there is less to transfer overall. Again, referencing the same Microsoft blog, the (total app size can be reduced by 87% [https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-3/]!
Smaller memory footprint: Code that does not need to worry about JIT and IL will have a smaller overall memory footprint. This can improve performance and on hosting!
Why Shouldn't I Use AOT?
The next valid question to ask, is what was the point of JIT and IL in the first place? Was the original .NET architecture flawed? The answer to this is obviously, no. AOT is probably not going to be applicable to every application (at least in the foreseeable short term). Just looking at the Microsoft documentation you can see that currently, AOT is not supported by a bunch of things including delegates, authentication, SignalR, MVC, and Blazor Server. This means that web developers won't be using AOT anytime soon! You can see a list of some of the things that are still outstanding here.
Aside from that, just like any other architecture AOT has good and bad bits:
As AOT code is all self-contained. Native AOT applications can not be run on systems that do not have the .NET runtime installed.
Currently, AOT is only supported to run on Windows.
No C++ support
AOT makes use of a technique called trimming. Trimming will try to remove IL code that is not statically reachable. The danger with trimming is that code can be removed that is needed. As it uses reflection on compile, you can not guarantee what will be trimmed. This quote is taken directly from the MSDN docs 'If you specify trimming as enabled from the command line, your debugging experience will differ and you may encounter additional bugs in the final product.' More information on trimming can be found here. It is also worth noting that there is also a self-contained app trimming mode!
Implies compilation into a single file, which has known incompatibilities
Apps include required runtime libraries (just like self-contained apps, increasing their size as compared to framework-dependent apps)
How To Use AOT?
When you update Visual Studio and install .NET 8, two new .NET project installation templates will be installed. If you do a search within the new project screen for
AOT you should see them:
The first template is an update to the "ASP.NET Core API" project template. Use this template if you are building an API project that is cloud-native. The second update is to the "ASP.NET Core gRPC Service" template. This also has a
Native AOT option now.
Personally, as a web developer, I do not need to use rRPC much so I'd say for the most part building your API layer using AOT is where a lot of developers will likely be making the most use of this feature for the foreseeable future. Remember, AOT is really good at booting up quickly, this is perfect for serverless functions and improving API response times.
In order to use either of these AOT native templates, you will need to make sure you have .NET 8 installed and an updated version of Visual Studio 2022 that supports it. You will also need to make sure you have
Desktop Development for C++ installed. If you do not have this, you will encounter a publishing error
Platform linker not found. Installing Desktop for C++ is easy enough, search for the Visual Studio installer and from the list find the option called
Desktop development with C++ within
Desktop and mobile:
After the project is created, you will need to perform a publish in order to generate the native AOT code. To do this within Visual Studio, you will create a publish profile. When setting up your publish profile, you will need to follow these settings:
Deployment mode needs to be self-contained. AOT does not support publishing using
The target runtime needs to be
win-64. AOT only works on Windows and currently does not support
After you perform a publish, the output files will look pretty similar to normal:
The big difference here is that it's all native code. Copy your files into the cloud and off you go!
AOT is an interesting feature, however, as it gets better integrated into the framework, using AOT will become more and more compelling as time progresses. Until AOT has full support, you need to be aware that AOT will not let you do everything. This is also why features like the new .NET 8 interceptors are being introduced now, as using
Assembly.LoadFile for dynamic loading on start-up is also not supported! As more and more features and alternative tools are added to the framework, you will be able to do more and more with AOT!