OwnageIsMagic / Polyfill

Source only package that exposes newer .net and C# features to older runtimes.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Polyfill

Build status Polyfill NuGet Status

Source only package that exposes newer .NET and C# features to older runtimes.

The package targets netstandard2.0 and is designed to support the following runtimes.

  • net461, net462, net47, net471, net472, net48, net481
  • netcoreapp2.0, netcoreapp2.1, netcoreapp3.0, netcoreapp3.1
  • net5.0, net6.0, net7.0, net8.0

See Milestones for release notes.

Nuget

https://nuget.org/packages/Polyfill/

SDK / LangVersion

This project uses features from the current stable SDK and C# language. As such consuming projects should target those:

LangVersion

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <LangVersion>latest</LangVersion>

global.json

{
  "sdk": {
    "version": "7.0.306",
    "rollForward": "latestFeature"
  }
}

Consuming and type visibility

The default type visibility for all polyfills is internal. This means it can be consumed in multiple projects and types will not conflict.

Consuming in an app

If Polyfill is being consumed in a solution that produce an app, then it is recommended to use the Polyfill nuget only in the root "app project" and enable PolyPublic.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <PolyPublic>true</PolyPublic>

Then all consuming projects, like tests, will not need to use the Polyfill nuget.

Consuming in a library

If Polyfill is being consumed in a solution that produce a library (and usually a nuget), then the Polyfill nuget can be added to all projects.

If, however, InternalsVisibleTo is being used to expose APIs (for example to test projects), then the Polyfill nuget should be added only to the root library project.

Included polyfills

ModuleInitializerAttribute

Reference: Module Initializers

static bool InitCalled;

[Test]
public void ModuleInitTest() =>
    Assert.True(InitCalled);

[ModuleInitializer]
public static void ModuleInit() =>
    InitCalled = true;

snippet source | anchor

IsExternalInit

Reference: init (C# Reference)

class InitExample
{
    public int Member { get; init; }
}

snippet source | anchor

Nullable attributes

Reference: Nullable reference types

Required attributes

Reference: C# required modifier

public class Person
{
    public Person() { }

    [SetsRequiredMembers]
    public Person(string name) =>
        Name = name;

    public required string Name { get; init; }
}

snippet source | anchor

CompilerFeatureRequiredAttribute

Indicates that compiler support for a particular feature is required for the location where this attribute is applied.

SkipLocalsInit

Reference: (SkipLocalsInit attribute)(https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/general#skiplocalsinit-attribute)

the SkipLocalsInit attribute prevents the compiler from setting the .locals init flag when emitting to metadata. The SkipLocalsInit attribute is a single-use attribute and can be applied to a method, a property, a class, a struct, an interface, or a module, but not to an assembly. SkipLocalsInit is an alias for SkipLocalsInitAttribute.

class SkipLocalsInitSample
{
    [SkipLocalsInit]
    static void ReadUninitializedMemory()
    {
        Span<int> numbers = stackalloc int[120];
        for (var i = 0; i < 120; i++)
        {
            Console.WriteLine(numbers[i]);
        }
    }
}

snippet source | anchor

Index and Range

Reference: Indices and ranges

If consuming in a project that targets net461 or net462, a reference to System.ValueTuple is required. See References: System.ValueTuple.

[TestFixture]
class IndexRangeSample
{
    [Test]
    public void Range()
    {
        var substring = "value"[2..];
        Assert.AreEqual("lue", substring);
    }

    [Test]
    public void Index()
    {
        var ch = "value"[^2];
        Assert.AreEqual('u', ch);
    }
}

snippet source | anchor

UnscopedRefAttribute

Reference: Low Level Struct Improvements

#if !NET7_0_OR_GREATER

using System.Diagnostics.CodeAnalysis;

struct UnscopedRefUsage
{
    int field;

    [UnscopedRef] ref int Prop1 => ref field;
}

#endif

snippet source | anchor

CallerArgumentExpressionAttribute

Reference: CallerArgumentExpression

using System.IO;

static class Guard
{
    public static void FileExists(string path, [CallerArgumentExpression("path")] string argumentName = "")
    {
        if (!File.Exists(path))
        {
            throw new ArgumentException($"File not found. Path: {path}", argumentName);
        }
    }
}

static class GuardUsage
{
    public static string[] Method(string path)
    {
        Guard.FileExists(path);
        return File.ReadAllLines(path);
    }
}

snippet source | anchor

InterpolatedStringHandler

References: String Interpolation in C# 10 and .NET 6, Write a custom string interpolation handler

StringSyntaxAttribute

Reference: .NET 7 - The StringSyntaxAttribute

Trimming annotation attributes

Reference: Prepare .NET libraries for trimming

Platform compatibility

Reference: Platform compatibility analyzer

StackTraceHiddenAttribute

Reference: C# – Hide a method from the stack trace

UnmanagedCallersOnly

Reference: Improvements in native code interop in .NET 5.0

SuppressGCTransition

DisableRuntimeMarshalling

Extensions

The class PolyfillExtensions includes the following extension methods:

Boolean

  • Boolean TryFormat(Span<Char>, Int32&) reference

Byte

  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider) reference

Dictionary<TKey,TValue>

  • Boolean Remove<TKey, TValue>(TKey, TValue&) reference

IEnumerable

  • IEnumerable<TSource[]> Chunk<TSource>(Int32) reference
  • IEnumerable<TSource> Except<TSource>(TSource) reference
  • IEnumerable<TSource> Except<TSource>(TSource[]) reference
  • IEnumerable<TSource> Except<TSource>(TSource, IEqualityComparer<TSource>) reference
  • IEnumerable<TSource> Except<TSource>(IEqualityComparer<TSource>, TSource[]) reference
  • TSource MaxBy<TSource, TKey>(Func<TSource,TKey>) reference
  • TSource MaxBy<TSource, TKey>(Func<TSource,TKey>, IComparer<TKey>) reference
  • TSource MinBy<TSource, TKey>(Func<TSource,TKey>) reference
  • TSource MinBy<TSource, TKey>(Func<TSource,TKey>, IComparer<TKey>) reference
  • IEnumerable<TSource> SkipLast<TSource>(Int32) reference

IReadOnlyDictionary<TKey,TValue>

  • TValue GetValueOrDefault<TKey, TValue>(TKey) reference
  • TValue GetValueOrDefault<TKey, TValue>(TKey, TValue) reference

KeyValuePair<TKey,TValue>

  • Void Deconstruct<TKey, TValue>(TKey&, TValue&) reference

DateTime

  • DateTime AddMicroseconds(Double) reference
  • Int32 Microsecond() reference
  • Int32 Nanosecond() reference
  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider) reference

DateTimeOffset

  • DateTimeOffset AddMicroseconds(Double) reference
  • Int32 Microsecond() reference
  • Int32 Nanosecond() reference
  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider) reference

Decimal

  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider) reference

Process

  • Task WaitForExitAsync(CancellationToken) reference

Double

  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider) reference

Int16

  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider) reference

Int32

  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider) reference

Int64

  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider) reference

Stream

  • Task CopyToAsync(Stream, CancellationToken) reference
  • ValueTask<Int32> ReadAsync(Memory<Byte>, CancellationToken) reference
  • ValueTask WriteAsync(ReadOnlyMemory<Byte>, CancellationToken) reference

TextReader

  • ValueTask<Int32> ReadAsync(Memory<Char>, CancellationToken) reference
  • Task<String> ReadLineAsync(CancellationToken) reference
  • Task<String> ReadToEndAsync(CancellationToken) reference

TextWriter

  • Void Write(ReadOnlySpan<Char>) reference
  • ValueTask WriteAsync(ReadOnlyMemory<Char>, CancellationToken) reference
  • Void WriteLine(ReadOnlySpan<Char>) reference
  • ValueTask WriteLineAsync(ReadOnlyMemory<Char>, CancellationToken) reference

HttpClient

  • Task<Byte[]> GetByteArrayAsync(String, CancellationToken) reference
  • Task<Byte[]> GetByteArrayAsync(Uri, CancellationToken) reference
  • Task<Stream> GetStreamAsync(String, CancellationToken) reference
  • Task<Stream> GetStreamAsync(Uri, CancellationToken) reference
  • Task<String> GetStringAsync(String, CancellationToken) reference
  • Task<String> GetStringAsync(Uri, CancellationToken) reference

HttpContent

  • Task<Byte[]> ReadAsByteArrayAsync(CancellationToken) reference
  • Task<Stream> ReadAsStreamAsync(CancellationToken) reference
  • Task<String> ReadAsStringAsync(CancellationToken) reference

ReadOnlySpan

  • Boolean EndsWith(String, StringComparison) reference
  • Boolean SequenceEqual(String) reference
  • Boolean StartsWith(String, StringComparison) reference

ReadOnlySpan

Reflection.EventInfo

  • Reflection.NullabilityState GetNullability()
  • Reflection.NullabilityInfo GetNullabilityInfo()
  • Boolean IsNullable()

Reflection.FieldInfo

  • Reflection.NullabilityState GetNullability()
  • Reflection.NullabilityInfo GetNullabilityInfo()
  • Boolean IsNullable()

Reflection.MemberInfo

  • Reflection.NullabilityState GetNullability()
  • Reflection.NullabilityInfo GetNullabilityInfo()
  • Boolean HasSameMetadataDefinitionAs(Reflection.MemberInfo) reference
  • Boolean IsNullable()

Reflection.ParameterInfo

  • Reflection.NullabilityState GetNullability()
  • Reflection.NullabilityInfo GetNullabilityInfo()
  • Boolean IsNullable()

Reflection.PropertyInfo

  • Reflection.NullabilityState GetNullability()
  • Reflection.NullabilityInfo GetNullabilityInfo()
  • Boolean IsNullable()

SByte

  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider) reference

Single

  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider) reference

Span

Span

String

StringBuilder

  • StringBuilder Append(ReadOnlySpan<Char>) reference
  • StringBuilder Append(AppendInterpolatedStringHandler&) reference
  • StringBuilder Append(IFormatProvider, AppendInterpolatedStringHandler&) reference
  • StringBuilder AppendLine(AppendInterpolatedStringHandler&) reference
  • StringBuilder AppendLine(IFormatProvider, AppendInterpolatedStringHandler&) reference
  • Void CopyTo(Int32, Span<Char>, Int32) reference
  • Boolean Equals(ReadOnlySpan<Char>) reference

Important

The methods using AppendInterpolatedStringHandler parameter are not extensions because the compiler prefers to use the overload with string parameter instead.

CancellationToken

  • CancellationTokenRegistration Register(Action<Object,CancellationToken>, Object) reference
  • CancellationTokenRegistration UnsafeRegister(Action<Object>, Object) reference
  • CancellationTokenRegistration UnsafeRegister(Action<Object,CancellationToken>, Object) reference

CancellationTokenSource

Task

Task

  • Task<TResult> WaitAsync<TResult>(CancellationToken) reference
  • Task<TResult> WaitAsync<TResult>(TimeSpan) reference
  • Task<TResult> WaitAsync<TResult>(TimeSpan, CancellationToken) reference

TimeSpan

Type

  • Boolean IsGenericMethodParameter() reference

UInt16

  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider) reference

UInt32

  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider) reference

UInt64

  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider) reference

References

If any of the below reference are not included, the related polyfills will be disabled.

System.ValueTuple

If consuming in a project that targets net461 or net462, a reference to System.ValueTuple nuget is required.

<PackageReference Include="System.ValueTuple"
                  Version="4.5.0"
                  Condition="$(TargetFramework.StartsWith('net46'))" />

System.Memory

If using Span APIs and consuming in a project that targets netstandard, netframework, or netcoreapp2*, a reference to System.Memory nuget is required.

<PackageReference Include="System.Memory"
                  Version="4.5.5"
                  Condition="$(TargetFrameworkIdentifier) == '.NETStandard' or
                             $(TargetFrameworkIdentifier) == '.NETFramework' or
                             $(TargetFramework.StartsWith('netcoreapp2'))" />

System.Threading.Tasks.Extensions

If using ValueTask APIs and consuming in a project that target netframework, netstandard2, or 'netcoreapp2', a reference to System.Threading.Tasks.Extensions nuget is required.

<PackageReference Include="System.Threading.Tasks.Extensions"
                  Version="4.5.4"
                  Condition="$(TargetFramework) == 'netstandard2.0' or
                             $(TargetFramework) == 'netcoreapp2.0' or
                             $(TargetFrameworkIdentifier) == '.NETFramework'" />

Nullability

Example target class

Given the following class

class NullabilityTarget
{
    public string? StringField;
    public string?[] ArrayField;
    public Dictionary<string, object?> GenericField;
}

snippet source | anchor

NullabilityInfoContext

[Test]
public void Test()
{
    var type = typeof(NullabilityTarget);
    var arrayField = type.GetField("ArrayField")!;
    var genericField = type.GetField("GenericField")!;

    var context = new NullabilityInfoContext();

    var arrayInfo = context.Create(arrayField);

    Assert.AreEqual(NullabilityState.NotNull, arrayInfo.ReadState);
    Assert.AreEqual(NullabilityState.Nullable, arrayInfo.ElementType!.ReadState);

    var genericInfo = context.Create(genericField);

    Assert.AreEqual(NullabilityState.NotNull, genericInfo.ReadState);
    Assert.AreEqual(NullabilityState.NotNull, genericInfo.GenericTypeArguments[0].ReadState);
    Assert.AreEqual(NullabilityState.Nullable, genericInfo.GenericTypeArguments[1].ReadState);
}

snippet source | anchor

NullabilityInfoExtensions

NullabilityInfoExtensions provides static and thread safe wrapper around NullabilityInfoContext. It adds three extension methods to each of ParameterInfo, PropertyInfo, EventInfo, and FieldInfo.

  • GetNullabilityInfo: returns the NullabilityInfo for the target info.
  • GetNullability: returns the NullabilityState for the state (NullabilityInfo.ReadState or NullabilityInfo.WriteState depending on which has more info) of target info.
  • IsNullable: given the state (NullabilityInfo.ReadState or NullabilityInfo.WriteState depending on which has more info) of the info:
    • Returns true if state is NullabilityState.Nullable.
    • Returns false if state is NullabilityState.NotNull.
    • Throws an exception if state is NullabilityState.Unknown.

Alternatives

PolySharp

https://github.com/Sergio0694/PolySharp

Combination of

Reason this project was created instead of using the above

PolySharp uses c# source generators. In my opinion a "source-only package" implementation is better because:

  • Simpler implementation
  • Easier to debug if something goes wrong.
  • Uses less memory at compile time. Since there is no source generator assembly to load.
  • Faster at compile time. Since no source generator is required to execute.

The combination of the other 3 packages is not ideal because:

  • Required multiple packages to be referenced.
  • Does not cover all the scenarios included in this package.

Icon

Crack designed by Adrien Coquet from The Noun Project.

About

Source only package that exposes newer .net and C# features to older runtimes.

License:MIT License


Languages

Language:C# 100.0%