Crossgen as build step with .NET Core 3.0

September 18, 2019 | 4 Minute Read

From NGEN to Crossgen. Use Crossgen to produce Ready-to-Run Images with dotnet publish.

.NET Conf 2019 Countdown series

I’m excited to be part of the .NET Conf with this every day mini-post series until the 23th September.

It’s definitely worth attending a .NET Conf 2019 local event to get together with other .NET friends. Join me on the 30th september at Community .NET Conf 2019 Event.

Prerequisites & Setup

You will need Visual Studio 2019 and .NET Core 3.0 SDK to try out this feature.

From NGEN to Crossgen

As you maybe know, your source code is compiled (by Roslyn) into IL-Code (CIL, Common Intermediate Language): a set of instructions (kind of low level language) that is platform- and processor independent. Unlike native code, before your .NET application can run, it requires a JIT (Just-in-Time) compiler that translates the IL-Code into native code (that is platform- and processor specific).

This JIT compilation (done by RyuJIT) into native code does take advantage of optimized or specialized instructions of the underlying platform/CPU and can also re-optimize methods on the fly (e.g. Tiered Compilation) - that’s great isn’t it?

But this process of JIT compilation isn’t free and is noticeable especially on the startup - as always software development is a trade-off. How could we mitigate that? A solution introduced nearly at the beginning of .NET era: the tool NGEN (.NET Framework 2.0 and onwards).

This NGEN compiled the .NET assembly into a special native image with native code. This concept is called AOT (ahead of time) compilation, kind of opposite of JIT. Unfortunately NGEN had by design the drawback, you couldn’t create the native images up-front as part of your application build and ship it to the client…

The successor: Crossgen

Let’s try out Crossgen that ships as part of .NET Core 3.0 with a console app:

    dotnet new console

It is simple to opt-in this feature - add the element <PublishReadyToRun>true</PublishReadyToRun> to your .csproj:

<Project Sdk="Microsoft.NET.Sdk">

After that, publish your console app:

    dotnet publish -c Release

Enabling log verbosity on MSBuild reveals the execution of specific targets to create the Ready-to-Run Images with Crossgen.exe:


When you look at the publish output directory, there is no notable difference, except the console app dll size:

  • Not Ready-To-Run: 4,00 KB (4.096 bytes)
  • Ready-To-Run: 5,50 KB (5.632 bytes)

The Crossgen processed assembly is slightly larger and has in the PE file header the flag CorFlags.ILLibrary=0x00000004 in contrast to the other assembly CorFlags.ILOnly=0x00000001.

Crossgen uses internally RyuJIT to compile the native code and includes it into your .NET assembly - but preserves the IL-Code. The IL-Code is kept for some scenarios and could also be used as fall-back when the native code doesn’t match the underlying platform. The native code reduces workload for the RyuJIT during startup-time of your application, thus it’s faster loaded.

Further, you could exclude assemblies from beeing processed by Crossgen within .csproj:

  <PublishReadyToRunExclude Include="MyAssembly.dll" />

Maybe you guest that already. You could combine Crossgen with the IL-Linker and The Bundler:

<Project Sdk="Microsoft.NET.Sdk">

The result would be a self contained, single file application with pre-compiled native code.

Limitations & .NET Native

  • ReadyToRun is supported on .NET 3.0 and later. You can’t use it with earlier versions (e.g. .NET Core 2.0 or .NET Framework).
  • ReadyToRun works only with SCD (Self-Contained Deployment) apps (FDD support is planned).
  • ReadyToRun does not support cross-targeting: For instance linux-x64 images must be compiled on that environment.

.NET Native is a completely separated tool-chain that is only usable for UWP (Universal Windows Platform) Apps. .NET Native doesn’t require any JIT and has a optimized and minimalistic runtime with some restrictions (e.g. Relfection API).


I’m super excited about some new capability that we get with .NET Core 3.0 SDK. This AOT option is worth trying especially for larger applications. Will you try out this for your application? Let me know, what you think.