Microsoft finally released version 1.0 of .NET Core in June 2016. .NET Core is a cross platform open source reimplementation of .NET Framework for the modern day. In spite of the name changes in the past, it seems recognizable enough in the developer community. One could hardly say that about .NET Standard, which is often mentioned in connection to it. Not many developers would dare to explain it with confidence. In this article, I will try to describe where it originates from, and why I think it is important for developers to know about it.
Update: The .NET Team has decided to keep .NET Standard 2.0 fully backward compatible with previous versions, i.e. the APIs introduced in .NET Standard 1.5 and 1.6 will remain a part of .NET Standard 2.0, although they are not implemented in .NET Framework 4.6.1. Read more over here: https://github.com/dotnet/standard/blob/master/docs/netstandard-20/README.md#net-framework-461-supporting-net-standard-20
This article is published from the DNC Magazine for Developers and Architects. Download this magazine from here [PDF] or Subscribe to this magazine for FREE and download all previous and current editions.
The Full .NET Framework
The first version of .NET framework was released in 2002. At the beginning of August 2016, it reached version 4.6.2. In more than 14 years since its initial release, there were a lot of improvements, but many core principles remained unchanged. Most importantly, it is only available for the Windows operating system, its desktop version to be precise. Windows even comes with a version of .NET framework, preinstalled. However, .NET framework updates, are independent of the operating system.
At the core of the .NET framework, there is the Common Language Runtime (CLR), a virtual machine executing the common Intermediate language (IL). IL is a replacement for platform and architecture specific machine code. On top of CLR is the Base Class Library (BCL) consisting of the base classes, and types. BCL is a part of the larger .NET Framework Class Library (FCL), which additionally includes the higher-level application frameworks, such as ASP.NET, WPF, LINQ, and others.
Image 1: .NET Framework Stack
In theory, this structure of .NET framework allows its executables to run on different platforms, as long as they all provide a CLR implementation, and have the same set of class libraries available for those applications. Unfortunately, it is a bit more complicated than that.
Alternative .NET Runtimes
Development of Silverlight applications was similar to development of .NET framework applications: the same languages could be used (C# and Visual Basic .NET), and a subset of .NET framework class libraries was available for it. The UI framework was XAML based (like WPF), but there were enough differences between the two to prevent sharing the same code. In short, Silverlight was a new independent .NET runtime, which had enough in common with .NET framework that existing .NET developers could learn it easily; but was different enough to make porting of existing applications difficult.
The trend continued with Windows Phone 7 in 2010. Its application framework was strongly based on Silverlight, yet was different enough to address the specifics of the new mobile platform. The platform evolved with new Windows Phone operating system versions until Windows Phone 8.1.
At that time, an alternative application framework for Windows Phone devices became available. It was based on WinRT (also known as Windows Runtime) - another spin-off of Silverlight, designed for development of Windows Store application for Windows 8, and its short-lived Windows RT version for ARM devices. This was Microsoft’s first attempt at a universal application platform for Windows desktop and phone devices.
With Windows 10, this runtime evolved into the Universal Windows Platform (UWP), a common application framework for all flavors of Windows 10 operating system and devices it runs on: desktop, mobile, IoT Core, Xbox One, HoloLens and Surface Hub.
The latest .NET runtime created by Microsoft was .NET Core. It is completely open source and currently available for Windows, Linux and macOS. UWP is actually running on top of it, but unlike .NET Core itself, it is not cross-platform. The only two cross-platform application models for .NET Core are console applications and ASP.NET Core. Hopefully, more will be available in the future.
Outside Microsoft, the Mono runtime is in active development in parallel to the .NET framework even before .NET framework was originally released 1.0 in 2002. It is an independent .NET framework compatible runtime with support for many different platforms viz Windows, Linux, BSD, all Apple operating systems, Android and even gaming consoles Sony PlayStation 3 and 4, and Nintendo Wii. It also serves as the basis for two other renowned products:
· Unity 3D game engine uses Mono runtime as the basis for its cross platform support, and allows its developers to use C# and a subset of .NET libraries for all the scripting in their games.
· Xamarin bases its Xamarin.iOS, Xamarin.Android and Xamarin.Mac products on Mono runtime for cross platform development using C# and .NET libraries.
In March 2016, Microsoft acquired Xamarin and became the owner of their products. In addition, both Unity and Mono are now part of .NET foundation, an independent organization dedicated to strengthening the .NET ecosystem. Hence, Microsoft is now more closely involved in development of all .NET runtimes than it has ever been before.
Challenges of Cross Platform Development
Due to the specifics of individual platforms, not all .NET applications can be packaged the same way. Some can be distributed as pure assemblies in IL, while others need to be wrapped into single platform specific packages, or even precompiled into machine code. However, this is not the main obstacle for effective cross platform development in .NET. The build process can take care of that easily enough.
A much bigger problem is the differences in class libraries that are available for individual .NET runtimes. This prevents the source code to be the same for all of them. There are two common approaches to cope with that problem:
· The code that needs to be different because of the differences in class libraries can be refactored into separate classes. For each runtime, these classes can then be implemented differently while the rest of the code can remain identical for all targeted runtimes.
· Where this approach is too granular, conditional compilation allows individual lines of code to differ for different runtimes in the same source code file. By defining different conditional compilation symbols for different runtimes, only the correct lines of code will be compiled for each runtime.
return "Windows Store application";
return "Windows Phone application";
Image 2: Conditional compilation symbols in project build properties
Portable Class Libraries
In 2011, Microsoft introduced Portable Class Libraries (PCL) to make cross platform development easier. The basic idea behind them was to define a common set of APIs that are available in all of the targeted runtimes. For this purpose, special contract assemblies were defined that could be referenced from PCL source code. At runtime, the calls to classes in those contract assemblies, are forwarded to the assemblies actually implementing those classes in each runtime. With this approach, the same PCL binaries work with different runtimes even if the classes are not implemented in the same assemblies in all of them, as long as they behave the same.
The first step in creating a portable class library involves selecting the exact runtimes, and their version that the library will target. Based on the selection, the common set of APIs across all of them is determined, and made available for use. Of course, the more runtimes the library targets, the smaller is the set of common APIs available.
Image 3: Targeted runtimes for a portable class library
This can help a lot in the following two scenarios:
· When developing a cross-platform application, common code can be put in a separate portable class library that only needs to be compiled once, and can then be referenced from other assemblies that target one of its selected target platforms. There is no need any more to have a different class library project for each platform, and include the same source code files in these multiple platform specific projects.
· Class library authors can build a single PCL assembly for all the platforms they want to target, and distribute it as a NuGet package that can be consumed on any supported platform.
There is a disadvantage to this approach, though. Only after explicitly selecting the target platforms and their versions in advance, the intersection of the available APIs for use in the class library is determined. This makes it impossible to target a newly released platform, or a new version of an existing platform, until all the tooling is updated. Even if its set of APIs matches one of the existing ones, developers must explicitly select the new target to support it. Existing libraries must also be recompiled for the new target, even if no source code change is required. All of this brings additional complexity and means more work for library authors.
.NET Standard is building on the basic concepts of portable class libraries, while trying to resolve their known issues. Instead of determining the common API for the targeted platforms, it defines a set of APIs independently of them. There are already multiple versions of .NET Standard defined. A higher version of standard is backward compatible with all previous versions. It only adds additional APIs to its previous version. Each existing platform supports .NET Standard up until a specific version:
Table 1: Minimum target platform version with support for each .NET Standard version
Class library authors can now select a .NET standard version to target with their library. A library targeting a specific .NET Standard version will work on any platform implementing this version of the standard. If a platform is updated to support a higher version of .NET Standard, or if a new platform is released, all existing libraries targeting the newly supported version of .NET Standard will automatically work with it without any changes required by their respective authors. Tooling will also only need to be updated when a new version of .NET Standard is released.
At the time of this writing, the process of creating a .NET Standard library in Visual Studio is still a bit unintuitive. You start by creating a new Portable Class Library project. It does not matter which targets you choose to support, as you will change that in the next step any way. When the new project is created, you need to open the project properties. At the bottom of the Library page, under the list of selected platforms there is a link to Target .NET Platform Standard.
Image 4: Target .NET Platform Standard
When you click on the link, a confirmation dialog will be displayed, warning you that you are about to change the library target which will result in a different set of available APIs.
Image 5: Confirmation dialog
Confirm the dialog and wait for the project to reload. The Targeting section of the Library page in project properties has now changed. You have the option to select a version of .NET Standard to target, along with a link to switch the targeting back to portable class library profiles.
Image 6: Select .NET Standard version to target
Depending on the APIs that you need, you can now select the version to target and start writing code. You can reference the created library from any other project targeting a compatible platform. Since I selected .NET Standard 1.4 in the screenshot above, I can reference my library from .NET Core projects, from UWP projects, and from classic Windows desktop projects for .NET framework 4.6.1 or above. If both projects are in the same solution, I can also add a reference to the project, not only to the compiled assembly. Just like any other cross project reference in Visual Studio, I can add it by checking the checkbox in front of the project in the Reference Manager dialog.
Image 7: Add reference to .NET Standard library project
If I try to reference it from an incompatible project (e.g. if I wanted to reference it from a .NET framework 4.6 project, which does not implement .NET Standard 1.4), Visual Studio would not allow it and would display an incompatibility warning instead.
Image 8: .NET Standard library cannot be referenced from an incompatible project
NuGet version 3.4 and above also supports .NET Standard libraries, i.e. class libraries can be packaged as targeting a version of .NET Standard, and can be consumed in other projects as such. This should make it possible for library authors to start targeting .NET Standard instead of PCL profiles, at least for the majority of PCL profiles, which have compatible .NET Standard equivalent.
.NET Standard 2.0
In its current state, .NET Standard is a cleaned up version of Portable Class Libraries. It has improved usability, but its set of available APIs is similarly limited. This prevents many of the existing class libraries to be ported to .NET Standard, and hence becoming available on other platforms. With the introduction of .NET Core, cross-platform development is becoming even more important, therefore it is the primary focus in the plans for .NET Standard 2.0.
.NET Standard 2.0 will initially be supported by .NET framework, the Xamarin platforms and .NET Core (this includes UWP, which is implemented on top of it). The set of APIs in .NET Standard 2.0 should be much larger than the existing set in .NET Standard 1.6. According to the current plan, all APIs that are present in both .NET framework and in Xamarin platforms, will be added to .NET Core 1.1. These APIs will also serve as candidates for adding to .NET Standard 2.0:
· Those that can be implemented on all platforms will be added to .NET Standard 2.0 as required APIs.
· Those that are platform specific or legacy, will be added to .NET Standard 2.0 as optional APIs.
Optional APIs will be made possible by the fact that just like PCL assemblies, .NET Standard assemblies also only do the type forwarding to the assemblies actually implementing the types. Implementations of optional APIs will not be a part of .NET Standard library. Instead, separate NuGet packages with their implementation will be available for supported platforms.
If such optional APIs will not be used in a project, additional NuGet packages will not need to be installed. However, as soon as an API will be used for the first time, the build will fail without the corresponding NuGet package installed. On platforms with support for the API, the issue will be resolved by installing the appropriate NuGet package. Visual Studio tooling should make the process of installing the right NuGet packages, easier. On non-supported platforms, the API will not be available for use. This approach should make it easier to develop for a subset of platforms with common optional APIs, e.g. to use Windows registry APIs in applications for .NET framework and .NET Core on Windows.
With a larger set of APIs available, it should become easier to port existing class libraries to .NET Standard. However, class libraries often depend on other class libraries, making them difficult to port until all of their dependencies are ported as well. To alleviate this problem, .NET Standard libraries will not be limited to referencing other .NET Standard libraries. Additionally, they will also be able to reference portable class libraries targeting compatible PCL profiles, and even .NET framework libraries that only use APIs included in .NET Standard 2.0.
To make consuming of .NET Standard libraries more convenient, a breaking change has been announced: .NET Standard 2.0 will be compatible with .NET Standard 1.4, but not with .NET Standard 1.5 and 1.6. APIs that were added in these two versions will be missing in .NET Standard 2.0, and will only be added back in later versions. The main reason for this decision is to make .NET Standard compatible with .NET framework 4.6.1, which does not implement the APIs that were added in.NET Standard 1.5 or 1.6. Since .NET framework is installed system-wide and cannot be bundled with applications, most computers will only have .NET framework 4.6.1 installed, because this version has already been distributed with a Windows 10 update. Developers who will therefore want to target this version, would not be able to use .NET Standard 2.0 libraries without this breaking change.
Understanding the details and inner workings of .NET Standard is most important to library authors and developers of cross platform applications. Both groups should already seriously consider changing their class libraries to target .NET Standard, instead of PCL profiles, where ever possible. Considering the fact that .NET Standard 2.0 will not be compatible with existing .NET Standard versions above 1.4, it makes most sense to target .NET Standard 1.4 until .NET Standard 2.0 is released.
Developers of applications that only target a single platform do not need to concern themselves with .NET Standard, just as they did not need to know about portable class libraries in the past. They might only see the term when checking compatibility of libraries they want to use, and even in this case, tooling should be able to protect them from implementation details.
In its current state, .NET Standard appears to be a promising successor to portable class libraries. It will show its full potential if .NET Standard 2.0 manages to deliver on all its promises.