With the release of .NET Standard 2.0 I thought it was time to convert FlacLibSharp to .NET Standard. FlacLibSharp is a .NET library I once made to read and write metadata of FLAC files.

Misunderstandings

After looking up some things on .NET Standard, I realised I had some incorrect assumptions:

  • Don't target the highest version

Targetting the highest version of .NET Standard, with a library, is not a good idea. There will be more platforms implementing the lower versions. Because they need to implement less of the API.

Each .NET Standard version adds more to the API. So the lowest .NET Version has the least API classes. For example, it lacks System.IO for file I/O.

If the goal is to have the library run on as many platforms as possible, it's best to target the lowest .NET Standard version that covers your needs.

  • You can't download .NET Standard 2.0

I don't know why, but it felt like .NET Standard was something like the .NET Framework or .NET Core. And you build your library against that framework. That is not the case.

.NET Framework and .NET Core are platforms. The .NET Standard only defines what API's need to be implemented.

While developing, you build a .NET library targetting a version of .NET Standard. The library will then run on all platforms that support that version of .NET Standard, or higher.

Porting Experience

Without further reading (d'oh!) I decided to blow the dust of the FlacLibSharp code and jump in.

The main issue I'm expecting is figuring out how to build and publish a NuGet package that can be used cross-platform.

But that's for part 2. First, I need to make sure there's a .NET Standard version my library can be built against.

Targetting a version

FlacLibSharp is still a .NET Framework 2.0 project.

Back then, I thought if I wrote it for .NET Framework 2.0, the most amount of people would be able to use it. Today, .NET Framework 2.0 is really old, so I wouldn't mind changing it to a higher version, if needed.

In Visual Studio: Open the solution, in the properties of the project, I cannot see an option to choose a .NET Standard version.

What needs to happen is, I must create a new ".NET Standard" project. When I create the project I must also choose the .NET Framework.

Question: If I build my class library for .NET Framework 4.6.2, but support .NET Standard 1.0, can the resulting dll of that project then be used by OLDER .NET Frameworks that implement that .NET Standard version?

I will create a new "Class Library (.NET Standard)" project, but use .NET Framework 4.6.2. Instead of copying the code, I will link the sources from the original .NET Framework 2.0 project, using Visual Studio.

My new solution now targets .NET Framework 4.6.2 and .NET Standard 1.4. The code is the same from my .NET Framework 2.0 project.

Not surprisingly, things are breaking. I receive 43 errors, mostly in the following style:

Error	CS0246	The type or namespace name 'SerializationInfo' could not be found (are you missing a using directive or an assembly reference?)	FlacLibSharp.DotNetStandard	C:\Users\user\Documents\Programming\github\flaclibsharp\FlacLibSharp\Exceptions\FlacLibSharpInvalidFormatException.cs	31	Active

In other words, I'm using a bunch of API's that are not supported.

Portability analyser

Microsoft provides a Visual Studio extension to analyse the "portability" of a project: the .NET Portability Analyzer

Beware, it can only run on projects that compile. So I ran it on the old .NET 2.0 project.

The results are kind of surprising because it tells me my project is fully compatible with ".NET Standard":

What happened: I forgot to define what versions to check on. This can be done via Visual Studio > "Tools > Options > .NET Portability Analyser".

By default, it checks for .NET Standard 2.0. You can also say which .NET Core and .NET Framework versions to analyse. I don't really care about the .NET Framework, for now I'll only choose .NET Standard 1.4. When I find out which .NET Standard version my code can use, I'll know which version of each platform I support based on this chart: .NET Standard Versions

The new analysis shows me my code is for 90,91% supported. The details of the report show what unsupported API's I'm using and from which .NET Standard version they are supported. It also sometimes recommends actions, like remove usage, increase the targetted .NET Standard version, ...

Here's a run down of the unsupported things I'm using, and what I did with them.

System.Runtime.Serialization.StreamingContext

This is only supported from .NET Standard 1.6.

I use it in the following code of my Exceptions:

[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
    if (info == null)
    {
        throw new ArgumentNullException("info");
    }
    base.GetObjectData(info, context);
    info.AddValue("Details", this.Details);
}

I honestly don't remember why this is there. But it's used to "set the SerializationInfo with information about the exception".

Basically it does some things to support Serialization of the exceptions with additional details. I'm going to remove this in the hopes of supporting .NET Core < 2.0. And also, I don't think this adds a lot of value.

I decided to remove GetObjectData overrides.

Good news, this increased support to 97 %. Bad news, if anyone is using that feature they will be unhappy. But really they can still custom serialize it if needed.

System.ApplicationException

This is supported in .NET Standard "2.0+". Also, when looking at the .NET Standard API reference for 2.0, the following important remark is there:

You should derive custom exceptions from the Exception class rather than the ApplicationException class. You should not throw an ApplicationException exception in your code, and you should not catch an ApplicationException exception unless you intend to re-throw the original exception.

So the solution here is to inherit from Exception instead of ApplicationException. This has no real consequences in the code. Maybe if anyone that is using my library and specifically looking for ApplicationException. But in that case, they should look for Exception.

This brings me up to 99 % support.

System.IO.Stream.Close

The Close method for Stream is only supported as of .NET Standard 2.0. Advice by the analyzer is to use the Dispose instead.

Easy fix!

Supported Platforms

This brings me up to code 100% supported on .NET Standard 1.4. This means the code will run - for sure - on the following platforms:

  • .NET Core 1.4
  • .NET Framework 4.6.1
  • Mono 4.6
  • Xamarin.iOS 10.0
  • Xamarin.Mac 10.0
  • Xamarin.Android 7.0
  • Universal Windows Platform 10.0

Important: Just because the code uses API's supported by these platforms, doesn't mean the code will run without issues. For example maybe my library does some file access that is not allowed on some platforms. In that case the supported API will throw an error.

It could also be that my code runs on older versions of these platforms. So to know for sure, I can run the analyser for all versions of all platforms. To do that analysis, the output must be HTML instead of Excel because Excel only supports 15 targets in the analysis, for some reason.

It looks like currently the library uses API's supported on these platforms:

  • .NET Core 1.0
  • .NET Standard 1.3
  • Mono 2.0
  • Universal Windows Platform 10.0
  • Xamarin.Android 1.0.0
  • Xamarin.iOS 1.0.0

It does not work on ANY version of Silverlight and Windows Phone.

I already knew it worked on .NET Framework 2.0.

If I look at what is missing in .NET Standard 1.2, it is System.IO. Not something my library can do without. So I'll be stuck at .NET Standard 1.3 as a minimum.

I could probably get it to work on Silverlight, but only if I want to ... I don't.

So I'll just change my project to indicate I'm targetting .NET Standard 1.3.

Still Build Errors

Having fixed these compatibility issues, I would've expected my .NET Standard project to build. Yet I still receive some build errors.

The type or namespace name 'Serializable' could not be found (are you missing a using directive or an assembly reference?)
The type or namespace name 'Serialization' does not exist in the namespace 'System.Runtime' (are you missing an assembly reference?)
The type or namespace name 'Permissions' does not exist in the namespace 'System.Security' (are you missing an assembly reference?)
The type or namespace name 'SerializableAttribute' could not be found (are you missing a using directive or an assembly reference?)
The type or namespace name 'Serializable' could not be found (are you missing a using directive or an assembly reference?)
The type or namespace name 'SerializableAttribute' could not be found (are you missing a using directive or an assembly reference?)

Some of these errors complain about my "using" directives referring the assemblies that don't exist in .NET Standard 1.3. For example using System.Runtime.Serialization.

For "Serializable", I'm using that attribute on the custom Exceptions:

[Serializable]
public class FlacLibSharpException : Exception

"Serializable" looks like it exists, also in .NET Standard 1.3: Type​Attributes.​Serializable Field

However, I'm not actually using "Serializable" but "SerializableAttribute". And this is only introduced with .NET Standard 2.0.

This seems to show the Compatibility Analyser isn't perfect.

Since I still want to support .NET Standard 1.3, I will stop using the Serializable attribute on my exception completely.

After this last bit, my .NET Standard 1.3 project now also compiles.

Conclusion

If you build a .NET library, you want to target the lowest version of .NET Standard possible.

You can use the Portability Analyser Visual Studio extension to know what version your code needs. But it's not perfect so also check for build errors.

To target a lower version of .NET Standard you may need to make compromises.

In my case, the most suitable version was .NET Standard 1.3. Mostly because of its support for System.IO.

In part 2, I look at creating a NuGet package for the library that can be used by all platforms that support .NET Standard 1.3.