yaakov.online

yaakov.online


I fight with computers

The Changing World of .NET

The following blog post is adapted from an internal presentation I gave at work a couple weeks ago.

Overview

In the last few years, Microsoft has shifted it's platform strategy drastically. Instead of being all about Windows, Microsoft has started to embrace the rest of the world. For example:

So it's of little surprise now that .NET runs everywhere. Through a mixture of .NET Framework, Mono, Xamarin, Unity and .NET Core, .NET can now run on:

Slowly but quietly, NET has grown from a very Microsoft-centred platform to one of the broadest, outside of Microsoft's reaches.

But before we get too carried away with all of this, what exactly is .NET? How does it work?

What is .NET?

For most developers who target .NET, it's a big box of magic.

C#, F# and Visual Basic developers will typically write some code, and then this magical thing called '.NET' takes over and compiles and executes the code.

If you dig into how it all actually works, there are three major components that hold a .NET app togethers:

Application Code

This is the code that you actually write to power your application. You can write some code in C#, F#, VB, Managed C++ (if you really want) or any other language that compiles to IL1 (also known as CIL or MSIL).

Base Class Libraries

Most of your application code, though, isn't actually code you've written. You're mostly re-using code from someone or somewhere else, and saying "fill in this stub later". For example, to parse a string into an int, you don't actually write parsing logic. You just use int.Parse or int.TryParse. All of this code is located inside the Base Class Libraries.

This typically forms part of the runtime. For example, the BCL code shipped with .NET Framework 4.5 is different to the BCL code shipped with .NET Framework 4.6, and some functions will behave slightly differently, or may have recieved performance optimizations and run faster.

Microsoft publish most of their own BCL code as Reference Source2.

Common Language Runtime

The runtime is what actually executes your code. CPUs don't understand IL, so this contains a Just-In-Time compiler which compiles the IL into machine instructions. The CLR also handles loading assemblies (DLLs/EXEs), Platform Invoke (P/Invoke, or invoking native code from .NET), and other similar low-level responsibilities.

Quiet Proliferation

However, the .NET Framework isn't the only BCL/CLR out there. In 2004, the Mono Project came along with an open-source reimplementation that today runs on Windows, macOS and Linux. It also forms the basis of the scripting engine in the Unity game engine.

In 2012, Xamarin used Mono as the foundation for a new .NET runtime, Xamarin.Mac, which adds APIs for developers to build Mac apps in .NET. In 2013, they expanded into Xamarin.iOS and Xamarin.Android, and have since added Apple watchOS and tvOS into the lineup.

And then, with the release of Windows 10 in 2015, Microsoft created the Universal Windows Platform which runs atop a technology named ".NET Core 5".

Project K

Around the same time, Microsoft were working on ASP.NET 5 / MVC 6, the successor to their ASP.NET 4 / MVC 6 platform for web development. Codenamed "Project K", this platform brought some major innovations that were not immediately backwards compatible:

Eventually, after a bit of soul-searching and realizing that - in the great Microsoft tradition - their naming sucked, Microsoft rebranded the lot:

.NET Core

Now that we've arrived at .NET Core, let's talk a little bit about it specifically.

.NET Core is a new runtime for .NET Core. It is not perfectly compatible with .NET Framework, and does not intend to be. It has new things that .NET Framework does not have, and Microsoft have intentionally removed features that they deemed to be "problematic":

Microsoft have stated that .NET Core will serve as "the foundation of all future .NET platforms." These are big words from a big company.

The pros of .NET Core are that:

On the flip side:

.NET Standard

As we've seen above, there are now a grand total of nine .NET runtimes, including the different variants of Xamarin. If you want to your code to run in as many places as possible, how do you do so? Do you have to write your code nine times? Do you have to compile it nine times?

Fortunately for most code, the answer is no. Microsoft have introduced something they confusingly named .NET Standard. In Microsoft's words:

.NET Standard is a specification that represents a set of APIs that all .NET platforms have to implement. This unifies the .NET platforms and prevents future fragmentation. Think of .NET Standard as POSIX for .NET.

In less confusing terms, .NET Standard is simply a set of APIs that runtime vendors can implement, and say "yes we support this version of .NET Standard".

For example, .NET Framework 4.5 implements .NET Standard 1.0 and 1.1 only. A .NET Standard 1.3 assembly is not guaranteed to run on .NET Framework 4.5, as it may use APIs that .NET Framework does not have. On the other hand, .NET Framework 4.6 implements the full set of APIs defined by .NET Standard 1.3, so you can safely run it there.

New SDK

Project Structure

To facilitate all these drastic changes comes a new SDK - actually, a second new SDK. Microsoft rebuilt the SDK around JSON-based projects, then gave up on it and reverted to MSBuild. The new SDK has much simpler projects than the old SDK, though.

With the old SDK, a clean project for a command-line app was 52 lines of XML. With the new SDK, it's just:

<Project Sdk="Microsoft.NET.Sdk">  
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp2.0</TargetFramework>
    </PropertyGroup>
</Project>  

That's it. All of the default junk that came with the old SDK - debug symbol types, default references, file alignment, optimization settings and so on - are now defaulted. You can specify them if you want to, to override the defaults, but for 99.99% of applications you won't need to, and probably never did.

Multi-Targeting

The new SDK also bring multi-targeting, i.e. one project file can product multiple executables for multiple platforms. To do this, edit the project file by renaming TargetFramework to TargetFrameworks. If you missed what the exact change is, you're pluralizing it.

Once pluralized, you can now fit multiple values in this field with standard MSBuild syntax, i.e.:

        <TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>

When multi-targeting, you have access to a bunch of conditional compiler directives. These come in three formats:

You can use these in [Conditional("...")] attributes or #if/#elif directives.

Dependency Management

With .NET Core and .NET Standard, everything is available exclusively on NuGet, the now-official .NET package manager.

Traditionally, this has been painful, but with the new SDK, NuGet has also gotten smarter.

NuGet references in projects now refer to the primary package or metapackage only. Your project no longer also contains a reference to every single transitive dependency in your dependency tree.

For me, this is the most significant change. I can now upgrade or uninstall a package without wondering what I'm leaving behind that I no longer need a reference to.

If you avoid using NuGet directly and use another tool instead, check to see if it supports the new SDK. At my work we use Paket3 extensively, and it has great support for the new SDK, target monikers and so forth.

Preparing for the Future

If your code targets .NET Framework now, you'd be pretty stupid not to keep .NET Core or .NET Standard as open possibilities for the future. To do so, there are a few things to keep in mind:

If you follow these simple steps, you should be in a pretty good place when the time eventually comes to port pieces of your codebase to .NET Standard, .NET Core, or whatever .NET Core-based platforms spring up in future.