The state of .NET Framework is that it's packaged with Windows so technically it did not require an install - an OS shipped with it.
The state of .NET is something that Java wants to reach in a few years or so. There are basically three ways to deliver an application to the users:
- A single or multiple files containing .NET assemblies and a thin binary launcher (single-file mode controls the exact shipping of this) - this is similar to OpenJDK which requires a runtime to be installed on the system (except OpenJDK does not have the capability to put all assemblies into a single file with a small native section for the launcher).
- A single or multiple files containing .NET assemblies and runtime itself
- A single native executable compiled with NativeAOT
The first two options allow to "merge" assemblies into a single file or ship them separately.
Shipping just .NET assemblies without runtime takes the least amount of space but requires a runtime dependency (it's easy to install on practically any system).
Shipping assemblies + runtime can also be done as a single file where there's a sandwich of native code and .NET assemblies. In order for such executable to not take disproportionate amount of space (65-120+ MiB) it can be trimmed which is recommended - this reduces base binary size down to 10 MiB or less nowadays (and grows as you add dependencies).
And building with NativeAOT relies on the same linker-based trimming but produces a single native statically linked executable. This results in the smallest runtime-independent binaries (they start at about 1-1.2MiB) with the best startup latency but comes with a different performance profile when compared to JIT and is not compatible with features that either rely on JIT or on unbound un-analyzable reflection where ILLink cannot determine the exact scope of code that has to be compiled. The ecosystem has significantly improved since the introduction of this target in .NET 7 to provide users with tools to make their code compatible, if any changes are required at all.
AOT compilation has existed for about 20 years, it only happened to be a commercial product, Excelsior JET, Aonix, PTC, Aicas, are some examples.
What GraalVM, OpenJ9 bring to the table is free beer AOT compilers, and in GraalVM's case, a LLVM like compiler framework that doesn't exist at all in .NET land.
They also have the advantage of using agents to gather the required reflection data, instead of forcing an ecosystem split of having to rewrite existing libraries to make them AOT ready.
There was the Phoenix project from Microsoft Research, which had the goal to replace Visual Studio infrastructure with a CLI based compiler toolchain, but unfortunely that project failed to gain traction within Microsoft.
Shipping runtimes, which .NET Core introduced for .NET, has been a thing in Java land since they exist.
There is also the ability to create single executables, coupled with jlink and jpackage.
Besides OpenJDK offerings, there is a richness of JVM implementations, each to those having other capabilities, like OpenJ9 and Azul with their distributed JIT compiler for example.
.NET is great and I prefer .NET to Java consulting projects when given the option, however it is no accident that Microsoft has decided to become again a Java vendor as well.
The state of .NET is something that Java wants to reach in a few years or so. There are basically three ways to deliver an application to the users:
- A single or multiple files containing .NET assemblies and a thin binary launcher (single-file mode controls the exact shipping of this) - this is similar to OpenJDK which requires a runtime to be installed on the system (except OpenJDK does not have the capability to put all assemblies into a single file with a small native section for the launcher).
- A single or multiple files containing .NET assemblies and runtime itself
- A single native executable compiled with NativeAOT
The first two options allow to "merge" assemblies into a single file or ship them separately.
Shipping just .NET assemblies without runtime takes the least amount of space but requires a runtime dependency (it's easy to install on practically any system).
Shipping assemblies + runtime can also be done as a single file where there's a sandwich of native code and .NET assemblies. In order for such executable to not take disproportionate amount of space (65-120+ MiB) it can be trimmed which is recommended - this reduces base binary size down to 10 MiB or less nowadays (and grows as you add dependencies).
And building with NativeAOT relies on the same linker-based trimming but produces a single native statically linked executable. This results in the smallest runtime-independent binaries (they start at about 1-1.2MiB) with the best startup latency but comes with a different performance profile when compared to JIT and is not compatible with features that either rely on JIT or on unbound un-analyzable reflection where ILLink cannot determine the exact scope of code that has to be compiled. The ecosystem has significantly improved since the introduction of this target in .NET 7 to provide users with tools to make their code compatible, if any changes are required at all.