12 January 2018

The problem with the .Net Standard compatibility shim…

The compatibility shim was added to .Net Standard to make it easier to reference libraries compiled using the .Net Framework. The idea was to help encourage adoption as NuGet was still overwhelmingly dominated by libraries that did not have a .Net Standard implementation.

The compatibility problem is caused by changing references between.Net Standard and the .Net Framework. For example, the Object class is defined in System.Runtime in .Net Core, while in .Net Framework it is implemented in mscorlib. If you try to use a .Net Framework assembly in a .Net Core application then the run-time will not be able to find the Object class because it cannot follow the reference to mscorlib.

The compatibility shim attempts to solve this by providing an implementation of mscorlib that forwards the reference to the correct implementation. This allows the .Net Core runtime to run a framework assembly and find all the basic types.

The problem with the shim

This shim only allows you to compile references built with the .Net Framework and it doesn’t guarantee that the libraries will actually work. Using the compatibility shim does require a certain leap of faith. It only bridges differences in assembly naming between .Net Standard and .Net Framework. It can’t fill in the gaps where types are missing completely.

The catch is that these missing types are only picked up at runtime. You can have a library that compiles perfectly well but still throws a FileNotFoundException when you try and run it as a referenced assembly is missing.

When you pull a .Net Framework assembly into your project with NuGet, it will compile but with the dreaded “This package may not be fully compatible with your project” warning. This is the compiler telling you that you can only verify the availability of all the types by running the project. This means that there is no way of guaranteeing compatibility without extensive run-time testing.

Until recently, the APIs missing from.Net Standard were very restrictive. The focus on cross-platform compatibility for .Net Standard means that any Windows-specific APIs were excluded. This includes commonly-used features such as registry access, event logs and performance counters as well as large swathes of IO and WCF.

This significantly reduced the range of libraries that could leverage the compatibility shim in any meaningful way. As soon as any forbidden feature is accessed then the application collapses with a FileNotFoundException.

Dealing with missing APIs

One option for dealing with missing APIs in a referenced library is to add the Windows Compatibility Pack for .Net Core to the host application. This is a NuGet package that adds support for many of these missing APIs to .Net Standard applications.

The package is really designed to support moving code bases from .Net Framework to .Net Standard. It can also be used to allow .Net Core applications to run legacy .Net Framework libraries by filling in the missing gaps that would otherwise generate a FileNotFoundException.

Note that many of these APIs have been deliberately left out of .Net Standard because they are specific to Windows. Attempting to call a Windows-only API in the compatibility pack on Linux will throw a PlatformNotSupportedException.

In most cases, the only workaround for a non-cross platform API is to refactor out the dependency. If you want to leverage the cross-platform aspects of .Net Core, then you will have to make sure that your APIs are compatible with .Net Standard. This is the only really guarantee that your code will work everywhere.

Microsoft do at least provide tooling that makes it pretty straightforward to identify these APIs. The API Analyzer identifies both deprecated and non cross-platform and can integrate with the Visual Studio IDE or a build server.

A fudge rather than a solution

Taken together, the shim and compatibility pack don’t provide a terribly robust foundation for cross-platform .Net Core development. They can only really provide a fudge that helps to incorporate legacy libraries that are too expensive to port over to .Net Standard.

You use this fudge at your own risk. Your application will be vulnerable to sudden failures at run-time caused by missing APIs. These APIs may be referenced deep in a dependency that you don’t have the source code for. In general, it’s better to port a library to .Net Standard if at all possible rather than relying on the uncertainty of the shim.

Filed under .Net Core, Net Framework.