Source only package that exposes newer .net and C# features to older runtimes.
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
https://nuget.org/packages/Polyfill/
This project uses features from the current stable SDK and C# language. As such consuming projects should target those:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<LangVersion>latest</LangVersion>
{
"sdk": {
"version": "7.0.306",
"rollForward": "latestFeature"
}
}
The default type visibility for all polyfills is internal
. This means it can be consumed in multiple projects and types will not conflict.
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.
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, InternalsVisibileTo
is being used to expose APIs (for example to test projects), then the Polyfill nuget should be added only to the root library project.
Reference: Module Initializers
static bool InitCalled;
[Test]
public void ModuleInitTest() =>
Assert.True(InitCalled);
[ModuleInitializer]
public static void ModuleInit() =>
InitCalled = true;
Reference: init (C# Reference)
class InitExample
{
private int member;
public int Member
{
get => member;
init => member = value;
}
}
Reference: Nullable reference types
Reference: C# required modifier
public class Person
{
public Person() { }
[SetsRequiredMembers]
public Person(string name) =>
Name = name;
public required string Name { get; init; }
}
Indicates that compiler support for a particular feature is required for the location where this attribute is applied.
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]);
}
}
}
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);
}
}
Reference: Low Level Struct Improvements
using System.Diagnostics.CodeAnalysis;
struct UnscopedRefUsage
{
int field;
[UnscopedRef] ref int Prop1 => ref field;
}
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);
}
}
Reference: Write a custom string interpolation handler
Reference: .NET 7 - The StringSyntaxAttribute
Reference: Prepare .NET libraries for trimming
Reference: Platform compatibility analyzer
Reference: C# – Hide a method from the stack trace
Reference: Improvements in native code interop in .NET 5.0
The class PolyfillExtensions
includes the following extension methods:
Boolean TryFormat(Span<Char>, Int32&)
reference
Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider)
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
TValue GetValueOrDefault<TKey, TValue>(TKey)
reference
TValue GetValueOrDefault<TKey, TValue>(TKey, TValue)
reference
Void Deconstruct<TKey, TValue>(TKey&, TValue&)
reference
DateTime AddMicroseconds(Double)
reference
Int32 Microsecond()
reference
Int32 Nanosecond()
reference
Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider)
reference
DateTimeOffset AddMicroseconds(Double)
reference
Int32 Microsecond()
reference
Int32 Nanosecond()
reference
Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider)
reference
Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider)
reference
Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider)
reference
Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider)
reference
Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider)
reference
Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider)
reference
Task CopyToAsync(Stream, CancellationToken)
reference
ValueTask<Int32> ReadAsync(Memory<Byte>, CancellationToken)
reference
ValueTask WriteAsync(ReadOnlyMemory<Byte>, CancellationToken)
reference
ValueTask<Int32> ReadAsync(Memory<Char>, CancellationToken)
reference
Task<String> ReadLineAsync(CancellationToken)
reference
Task<String> ReadToEndAsync(CancellationToken)
reference
Void Write(ReadOnlySpan<Char>)
reference
ValueTask WriteAsync(ReadOnlyMemory<Char>, CancellationToken)
reference
Void WriteLine(ReadOnlySpan<Char>)
reference
ValueTask WriteLineAsync(ReadOnlyMemory<Char>, CancellationToken)
reference
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
Task<Byte[]> ReadAsByteArrayAsync(CancellationToken)
reference
Task<Stream> ReadAsStreamAsync(CancellationToken)
reference
Task<String> ReadAsStringAsync(CancellationToken)
reference
Boolean EndsWith(String, StringComparison)
reference
Boolean SequenceEqual(String)
reference
Boolean StartsWith(String, StringComparison)
reference
Boolean Contains<T>(T)
reference
Reflection.NullabilityState GetNullability()
Reflection.NullabilityInfo GetNullabilityInfo()
Boolean IsNullable()
Reflection.NullabilityState GetNullability()
Reflection.NullabilityInfo GetNullabilityInfo()
Boolean IsNullable()
Reflection.NullabilityState GetNullability()
Reflection.NullabilityInfo GetNullabilityInfo()
Boolean HasSameMetadataDefinitionAs(Reflection.MemberInfo)
reference
Boolean IsNullable()
Reflection.NullabilityState GetNullability()
Reflection.NullabilityInfo GetNullabilityInfo()
Boolean IsNullable()
Reflection.NullabilityState GetNullability()
Reflection.NullabilityInfo GetNullabilityInfo()
Boolean IsNullable()
Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider)
reference
Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider)
reference
Boolean EndsWith(String)
reference
Boolean SequenceEqual(String)
reference
Boolean StartsWith(String)
reference
Boolean Contains<T>(T)
reference
Boolean Contains(String, StringComparison)
reference
Boolean Contains(Char)
reference
Void CopyTo(Span<Char>)
reference
Boolean EndsWith(Char)
reference
Int32 GetHashCode(StringComparison)
reference
String[] Split(Char, StringSplitOptions)
reference
String[] Split(Char, Int32, StringSplitOptions)
reference
Boolean StartsWith(Char)
reference
Boolean TryCopyTo(Span<Char>)
reference
StringBuilder Append(ReadOnlySpan<Char>)
reference
Void CopyTo(Int32, Span<Char>, Int32)
reference
Boolean Equals(ReadOnlySpan<Char>)
reference
Task CancelAsync()
reference
Task WaitAsync(CancellationToken)
reference
Task WaitAsync(TimeSpan)
reference
Task WaitAsync(TimeSpan, CancellationToken)
reference
Task<TResult> WaitAsync<TResult>(CancellationToken)
reference
Task<TResult> WaitAsync<TResult>(TimeSpan)
reference
Task<TResult> WaitAsync<TResult>(TimeSpan, CancellationToken)
reference
Boolean IsGenericMethodParameter()
reference
Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider)
reference
Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider)
reference
Boolean TryFormat(Span<Char>, Int32&, ReadOnlySpan<Char>, IFormatProvider)
reference
If any of the below reference are not included, the related polyfills will be disabled.
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'))" />
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'))" />
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'" />
Given the following class
class NullabilityTarget
{
public string? StringField;
public string?[] ArrayField;
public Dictionary<string, object?> GenericField;
}
[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);
}
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:
NullabilityState.Nullable
.NullabilityState.NotNull
.NullabilityState.Unknown
.https://github.com/Sergio0694/PolySharp
PolySharp uses c# source generators. In my opinion a "source-only package" implementation is better because:
The combination of the other 3 packages is not ideal because:
Crack designed by Adrien Coquet from The Noun Project.