Polyfills in .NET to ease multi-targeting

 
 
  • Gérald Barré

When you write a .NET library, you may want to target multiple target framework monikers (TFM). For instance, you may want to target .NET 6 and .NET Standard 2.0. This allows your library to be used by more applications, By targeting the latest supported framework, you can also use Nullable Reference Types. However, it can be challenging to write a library that targets multiple TFMs. For instance, you may want to use a new API that is not available in the older TFM. Or you may want to use a new C# feature that is not available in the older TFM. So, you may need to use lots of #if, which reduces the readability of the code. For instance, the following code uses #if to use the Process.WaitForExitAsync method if it is available. Otherwise, it uses the Process.WaitForExit method.

C#
public async Task Sample()
{
    var process = Process.Start("sample.exe");

#if NET6_0_OR_GREATER
    await process.WaitForExitAsync();
#else
    process.WaitForExit();
#endif
}

In this post, I describe how to simplify writing libraries that target multiple TFMs by using polyfills.

#What is a polyfill?

A polyfill is a piece of code that provides the same functionality as a new API or a new C# feature. For instance, the String.Contains(char) method is not available in .NET Standard 2.0. However, you can write a polyfill that provides the same functionality as the String.Contains(char) method using an extension method. Then, you can use the polyfill in your library. The polyfill will be used when the String.Contains(char) method is not available. Otherwise, the String.Contains(char) method will be used.

#What can be polyfilled?

You can polyfill

  • New types (class, interface, struct, enum)
  • New instance methods by using extension methods

You cannot polyfill

  • New static methods on existing types
  • Features that require runtime support

#How to install polyfills

Multiple NuGet packages provide polyfills:

I'll use the Meziantou.Polyfill package in this post 😉 That doesn't mean other packages are not great, but I'm the author of this one.

Meziantou.Polyfill is a source generator, so it can look at your code and add only what is needed. This means that each target may get different polyfills. For instance, if you target .NET 6, you may not need to polyfill the String.Contains(char) method. However, if you target .NET Standard 2.0, you may need to polyfill the String.Contains(char) method.

At the time of writing, the package provides about 100 polyfills (types or methods). So, you can use C# features such as record or ranges (array[1..2]), attributes to improve your code such as [StringSyntax(StringSyntaxAttribute.Json)], LINQ methods such as Enumerable.Order, new methods on many other types such as String, Stream, ImmutableArray.

Note that the implementation of the polyfill cannot always provide the same performance as the implementation from the .NET libraries. However, it should be ok most of the time. Also, if you care about performance, you should probably not target old TFMs. So, the polyfill performance should not be an issue.

Shell
dotnet add package Meziantou.Polyfill

You can check which polyfills are generated by TFM in the solution explorer:

You can now write the rewrite the initial by removing the #if and compile the code. The compiler should be happy!

C#
public async Task Sample()
{
    var process = Process.Start("sample.exe");
    await process.WaitForExitAsync();
}

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub