Skip to content

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

License

Notifications You must be signed in to change notification settings

skarllot-forking/Polyfill

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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, net9.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: SkipLocalsInitAttribute

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 Polyfill includes the following extension methods:

Important

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

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

HashSet

IEnumerable

  • IEnumerable<KeyValuePair<TKey,TAccumulate>> AggregateBy<TSource, TKey, TAccumulate>(Func<TSource,TKey>, TAccumulate, Func<TAccumulate,TSource,TAccumulate>, IEqualityComparer<TKey>) reference
  • IEnumerable<KeyValuePair<TKey,TAccumulate>> AggregateBy<TSource, TKey, TAccumulate>(Func<TSource,TKey>, Func<TKey,TAccumulate>, Func<TAccumulate,TSource,TAccumulate>, IEqualityComparer<TKey>) reference
  • IEnumerable<TSource[]> Chunk<TSource>(Int32) reference
  • IEnumerable<KeyValuePair<TKey,Int32>> CountBy<TSource, TKey>(Func<TSource,TKey>, IEqualityComparer<TKey>) reference
  • IEnumerable<ValueTuple<Int32,TSource>> Index<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
  • HashSet<TSource> ToHashSet<TSource>(IEqualityComparer<TSource>) 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

List

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

Guid

  • Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>) 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

Regex

  • ValueMatchEnumerator EnumerateMatches(ReadOnlySpan<Char>) reference
  • ValueMatchEnumerator EnumerateMatches(ReadOnlySpan<Char>, Int32) reference
  • Boolean IsMatch(ReadOnlySpan<Char>, Int32) reference
  • Boolean IsMatch(ReadOnlySpan<Char>) reference

StringBuilder

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

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

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

Static helpers

EnumPolyfill

RegexPolyfill

  • ValueMatchEnumerator EnumerateMatches(ReadOnlySpan<Char>, String) reference
  • ValueMatchEnumerator EnumerateMatches(ReadOnlySpan<Char>, String, RegexOptions, TimeSpan) reference
  • ValueMatchEnumerator EnumerateMatches(ReadOnlySpan<Char>, String, RegexOptions) reference
  • Boolean IsMatch(ReadOnlySpan<Char>, String, RegexOptions, TimeSpan) reference
  • Boolean IsMatch(ReadOnlySpan<Char>, String, RegexOptions) reference
  • Boolean IsMatch(ReadOnlySpan<Char>, String) reference

StringPolyfill

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

Enable by adding and MSBuild property PolyNullability

<PropertyGroup>
  ...
  <PolyNullability>true</PolyNullability>
</PropertyGroup>

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

PolyShim

https://github.com/Tyrrrz/PolyShim

PolySharp

https://github.com/Sergio0694/PolySharp

Theraot.Core

https://github.com/theraot/Theraot

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.

Notes

Icon

Crack designed by Adrien Coquet from The Noun Project.

About

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

Resources

License

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C# 100.0%