Polyfills for projects targeting older versions of .NET
MIT License
PolyShim is a collection of polyfills that enable many modern BCL and compiler features for projects targeting older versions of .NET. It's distributed as a source-only package that can be referenced without imposing any run-time dependencies.
By using this project or its source code, for any purpose and in any shape or form, you grant your implicit agreement to all the following statements:
To learn more about the war and how you can help, click here. Glory to Ukraine! πΊπ¦
dotnet add package PolyShim
Important: To use this package, you must have the latest version of the .NET SDK installed. This is only required for the build process, and does not affect which version of the runtime you can target.
ValueTuple<...>
Index
and Range
HashCode
SkipLocalsInitAttribute
CallerArgumentExpressionAttribute
ExcludeFromCodeCoverageAttribute
PolyShim polyfills come in two forms:
Once the package is installed, the polyfills will be automatically added to your project as internal source files. You can then use them in your code by referencing the corresponding types or methods as if they were defined natively.
Note: Polyfills are only applied to types and methods that are not already provided. When a native implementation of a symbol is available β either in the framework or through a referenced compatibility package β it will always be prioritized over the corresponding polyfill.
PolyShim provides various types that are not available natively on older target frameworks.
These types are defined within the corresponding System.*
namespaces and mimic the behavior of their original implementations as closely as possible.
For example, with PolyShim you can use the Index
and Range
structs (added in .NET Core 3.0) on any older version of .NET:
using System;
// On older framworks, these are provided by PolyShim
var index = new Index(1, fromEnd: true);
var range = new Range(
new Index(3),
new Index(1, true)
);
You can also use compiler features that rely on these types, such as the advanced indexing and slicing operators:
var array = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// On older framworks, these are provided by PolyShim
var last = array[^1];
var part = array[3..^1];
PolyShim provides a number of extension methods that act as shims for instance methods that are not available natively on older target frameworks.
These extension methods are typically defined within the global namespace, so they can be called on the corresponding types just like instance methods, without any additional using
directives.
For example, with PolyShim you can use the String.Contains(char)
method (added in .NET Core 2.0) on any older version of .NET:
var str = "Hello world";
// On older framworks, this is provided by PolyShim
var contains = str.Contains('w');
Some features from newer versions of .NET can also be made available on older frameworks using official compatibility packages published by Microsoft. PolyShim automatically detects if any of these packages are installed and adjusts its polyfill coverage accordingly β either by enabling additional polyfills that build upon those features, or by disabling polyfills for APIs that are already provided in the compatibility packages.
Currently, PolyShim has integration with the following packages:
System.Diagnostics.Process
β Process
, ProcessStartInfo
, etc.System.Management
β ManagementObjectSearcher
, etc.System.Memory
β Memory<T>
, Span<T>
, etc.System.Net.Http
β HttpClient
, HttpContent
, etc.System.Runtime.InteropServices.RuntimeInformation
β RuntimeInformation
, OSPlatform
, etc.System.Threading.Tasks
β Task
, Task<T>
, etc.System.Threading.Tasks.Extensions
β ValueTask
, ValueTask<T>
, etc.System.ValueTuple
β ValueTuple<...>
, etc.Microsoft.Bcl.Async
β Task
, Task<T>
, etc (wider support than the System.*
variant).Microsoft.Bcl.AsyncInterfaces
β IAsyncEnumerable<T>
, IAsyncDisposable
, etc.Microsoft.Bcl.HashCode
β HashCode
, etc.Microsoft.Net.Http
β HttpClient
, HttpContent
, etc (wider support than the System.*
variant).For example, adding a reference to the System.Memory
package will enable PolyShim's polyfills that offer Span<T>
and Memory<T>
-based method overloads on various built-in types, such as Stream
:
<Project>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PolyShim" Version="*" />
<PackageReference Include="System.Memory" Version="4.5.5" />
</ItemGroup>
</Project>
using System.Buffers;
using System.IO;
using var stream = /* ... */;
using var buffer = MemoryPool<byte>.Shared.Rent(512);
// System.Memory is referenced, so this polyfill is enabled
var bytesRead = await stream.ReadAsync(buffer.Memory);
Conversely, adding a reference to the System.ValueTuple
package will disable PolyShim's own version of ValueTuple<...>
and related types.
You can leverage this to prioritize the official implementation wherever possible, while still relying on the polyfilled version for older target frameworks:
<Project>
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PolyShim" Version="*" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
</Project>
// System.ValueTuple is referenced, so this polyfill is disabled
// (the native implementation is used instead)
var (x, y) = ("hello world", 42);
Despite best efforts, PolyShim is not able to polyfill all the missing APIs due to limitations in the C# language. At least until some form of the Extension Everything feature is implemented, below are some of the things that currently cannot be polyfilled: