| Development of this project is entirely funded by the community. Consider donating to support! |
PolyShim is a collection of polyfills that enable many modern framework APIs 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.
Terms of use[?]
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:
- You condemn Russia and its military aggression against Ukraine
- You recognize that Russia is an occupant that unlawfully invaded a sovereign state
- You support Ukraine's territorial integrity, including its claims over temporarily occupied territories of Crimea and Donbas
- You reject false narratives perpetuated by Russian state propaganda
To learn more about the war and how you can help, click here. Glory to Ukraine! πΊπ¦
- π¦ NuGet:
dotnet add package PolyShim
Important
To reference this package, you must have the latest major 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.
- Enables compiler support for:
- Nullable reference types
- Record types
- Init-only properties
- Required properties
- Value tuples
- Index and range operators
- Caller information
- Module initializers
- Overload priority
- Provides type polyfills for:
ValueTuple<...>IndexandRangeSpan<T>andMemory<T>LockHashCodeArrayPool<T>TaskCompletionSource- ...and 70+ more
- Provides member polyfills for:
string.ReplaceLineEndings(...),string.AsSpan(), etc.Stream.ReadExactly(...),Stream.ReadAtLeast(...), etc.IEnumerable<T>.Chunk(...),IEnumerable<T>.TakeLast(...), etc.Task.WaitAsync(...),Task.WhenEach(...), etc.Parallel.ForEachAsync(...),Parallel.ForAsync(...), etc.File.WriteAllTextAsync(...),File.ReadAllTextAsync(...), etc.Environment.ProcessPath,Environment.ProcessId, etc.OperatingSystem.IsWindows(),OperatingSystem.IsLinux(), etc.- ...and 250+ more
- Adjusts polyfills based on available capabilities
- Targets .NET Standard 1.0+, .NET Core 1.0+, .NET Framework 3.5+
- Imposes no run-time dependencies
PolyShim polyfills come in two forms:
- Type polyfills, which define missing built-in types by reimplementing them from scratch.
- Member polyfills, which are provided through global extension members that substitute missing members on existing built-in types.
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 members as if they were defined natively.
Note
Polyfills are only applied to types and members 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 version of .NET:
using System;
// On newer frameworks, this references the framework-provided types.
// On older frameworks, this references the polyfilled types.
// Same code works everywhere without any changes.
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 newer frameworks, these operators rely on the framework-provided types.
// On older frameworks, these operators rely on the polyfilled types.
// Same code works everywhere without any changes.
var last = array[^1];
var part = array[3..^1];Note
You can find the full list of type polyfills here.
PolyShim provides a number of extension members that act as shims for instance or static members that are not available natively on older target frameworks.
These extension members are defined within the global namespace, so they can be used on the corresponding types just like intrinsic members, without any additional using directives.
For example, with PolyShim you can reference the Environment.ProcessId static property (added in .NET 5.0) on any version of .NET:
using System;
// On newer frameworks, this references the framework-provided property.
// On older frameworks, this references the polyfilled (extension) property.
// Same code works everywhere without any changes.
var processId = Environment.ProcessId;Note
You can find the full list of member polyfills here.
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 recognizes 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 theSystem.*variant).Microsoft.Bcl.AsyncInterfacesβIAsyncEnumerable<T>,IAsyncDisposable, etc.Microsoft.Bcl.HashCodeβHashCode, etc.Microsoft.Bcl.MemoryβIndex,Range, etc.Microsoft.Net.HttpβHttpClient,HttpContent, etc. (wider support than theSystem.*variant).
For example, adding a reference to the Microsoft.Bcl.AsyncInterfaces package will enable PolyShim's polyfills that work with IAsyncEnumerable<T>, such as Task.WhenEach(...):
<Project>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PolyShim" Version="..." />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="..." />
</ItemGroup>
</Project>using System;
using System.Linq;
using System.Threading.Tasks;
var tasks = Enumerable.Range(1, 10).Select(async i =>
{
await Task.Delay(Random.Shared.Next(1000));
return i * i;
});
// Microsoft.Bcl.AsyncInterfaces is referenced, so this polyfill is enabled
await foreach (var completedTask in Task.WhenEach(tasks))
{
Console.WriteLine(await completedTask);
}Conversely, adding a reference to the System.Memory package will disable PolyShim's own versions of Span<T> and Memory<T>.
You can leverage this to prioritize the official implementation wherever possible, while still benefiting from other polyfills provided by PolyShim:
<Project>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PolyShim" Version="..." />
<PackageReference Include="System.Memory" Version="..." />
</ItemGroup>
</Project>using System;
// System.Memory is referenced, so this polyfill is disabled
// (the official Span<T> and Memory<T> types are used instead)
Span<byte> buffer = stackalloc byte[256];
Random.Shared.NextBytes(buffer);