Latest packages | NuGet | MyGet |
---|---|---|
PolyKit | ||
PolyKit.Embedded |
Hi there! I'm Riccardo a.k.a. @rdeago, founder of Tenacom and author of PolyKit.
I won't insult your intelligence by explaining what a polyfill, a package reference, or a MSBuild property is. If these are new concepts to you, well... we both know you have a browser and aren't afraid to use it. 😉
Throughout this document, I'll assume that you know what follows:
- what is a polyfill and when you need polyfills;
- how to create a C# project;
- the meaning of
TargetFramework
(orTargetFrameworks
) in the context of a.csproj
file; - the meaning of
PackageReference
in the context of a.csproj
file; - the meaning of
PrivateAssets="all"
in aPackageReference
; - how to make minor modifications to a project file, for example adding a property or an item.
If you're yawning, great, read on. If you're panicking, do your googling due diligence and come back. I'll wait, no problem.
PolyKit is both a run-time library (provided via the PolyKit
NuGet package) and a set of ready-to-compile source files (provided via the PolyKit.Embedded
NuGet package) that add support for latest C# features as well as recent additions to the .NET runtime library, even in projects targeting .NET Framework or .NET Standard 2.0.
How you use PolyKit depends on your project:
- for a single-project application or library, such as a simple console program or a source generator DLL,
internal
polyfills provided by thePolyKit.Embedded
package will suit just fine; - for a library whose public-facing APIs may require polyfills, for example if some
public
method accepts or returnsSpan
s,public
polyfills provided by thePolyKit
package will polyfill dependent applications where necessary, and get out of the way on platforms that do not require them; - for a multi-project solution, with some application and several shared libraries, you may want to avoid code duplication by using the
public
polyfills provided by thePolyKit
package; - if your solution contains a "core" library, referenced by all other projects, you may even spare an additional dependency by incorporating
public
polyfills in your own library.
PolyKit employs the same technique used by the .NET Standard library to expose public
types on older frameworks without creating conflicts on newer ones: types that need no polyfilling (for example the StringSyntax
attribute on .NET 5+) are forwarded to their .NET runtime implementation.
This way you can safely reference PolyKit
and use, even expose, polyfilled types in a .NET Standard 2.0 library, because the actual type used will depend upon the target framework of each application.
The same goes for public
polyfills created by PolyKit.Embedded
. In this case, however, to ensure that no type conflicts happen at runtime, you should multi-target according to the application target frameworks you want to support. For example, if your library targets .NET Standard 2.0 only, a .NET 6.0 application will "see" two identical StringSyntax
types: one in the .NET runtime and the other in PolyKit.dll
.
The solution is simple: when using PolyKit.Embedded
to add public
polyfills to a library, set your TargetFrameworks
property so that you generate all possible sets of polyfills.
The optimal set of target frameworks for public
polyfills is equal to the target frameworks of the PolyKit
package. At the time of writing it is net462;net47;netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0
, but you may refer to this NuGet page at any time to find out which frameworks are targeted by the latest version of PolyKit
.
PolyKit
and PolyKit.Embedded
require that you build dependent projects with .NET SDK version 6.0 or later.
Polyfills provided by PolyKit
and PolyKit.Embedded
are compatible with all flavors of .NET supported by Microsoft at the time of publishing, as well as all corresponding versions of .NET Standard:
- .NET Framework 4.6.2 or greater;
- .NET 6 or greater;
- .NET Standard 2.0 or greater.
A minimum of Visual Studio / Build Tools 2022 and/or .NET SDK 6.0 is required to compile the polyfills provided by PolyKit.Embedded
.
C# language version 10.0 or greater is required to compile the polyfills provided by PolyKit.Embedded
.
It is recommended to set the LangVersion
property to latest
in projects that reference PolyKit
or PolyKit.Embedded
, in order to take advantage of all the polyfilled features.
All code provided by PolyKit.Embedded
is well-behaved guest code:
- all source files bear the "auto-generated" mark, so that code style analyzers will happily skip them;
- every source file name ends in
.g
(e.g.Index.g.cs
) so that it can be automatically excluded from code coverage; - all added types are marked with a
GeneratedCode
attribute; - all added classes and structs are marked with
ExcludeFromCodeCoverage
andDebuggerNonUserCode
attributes.
This ensures that using PolyKit.Embedded
will have zero impact on your coverage measurements, code metrics, analyzer diagnostic output, and debugging experience.
PolyKit provides support for the following features across all compatible target frameworks.
Please note that some types will only be polyfilled when compiling with a .NET SDK version that actually supports them.
Feature | Minimum .NET SDK version | Notes |
---|---|---|
nullable reference types, including null state analysis | 6.0 | |
indices and ranges | 6.0 | Note #1 |
init accessor on properties and indexers |
6.0 | |
DateOnly struct |
6.0 | Note #2 |
TimeOnly struct |
6.0 | Note #2 |
HashCode struct |
6.0 | Note #3 |
caller argument expressions | 6.0 | |
AsyncMethodBuilder attribute |
6.0 | |
ModuleInitializer attribute |
6.0 | |
SkipLocalsInit attribute |
6.0 | |
UnconditionalSuppressMessage attribute |
6.0 | |
RequiresPreviewFeatures attribute |
6.0 | |
UnmanagedCallersOnly attribute |
6.0 | |
ValidatedNotNull attribute |
6.0 | Note #4 |
StackTraceHidden attribute |
6.0 | Note #5 |
support for writing custom string interpolation handlers | 6.0 | |
Enumerable.TryGetNonEnumeratedCount<TSource> method |
6.0 | Note #6 |
ISpanFormattable interface |
6.0 | Note #7 |
trimming incompatibility attributes | 6.0 / 7.0 | Note #8 |
required members | 7.0 | |
scoped modifier (including the UnscopedRef attribute) |
7.0 | |
CompilerFeatureRequired attribute |
7.0 | |
ConstantExpected attribute |
7.0 | |
StringSyntax attribute |
7.0 | |
Experimental attribute |
8.0 |
Note #1: This feature depends on System.ValueTuple
, which is not present in .NET Framework versions prior to 4.7. If you reference the PolyKit.Embedded
package in a project targeting .NET Framework 4.6.2, you must also add a package reference to System.ValueTuple
; otherwise, compilation will not fail, but features dependent on ValueTuple will not be present in the compiled assembly.
Note #2: Polyfills for DateOnly
and TimeOnly
, unlike their .NET Runtime counterparts, do not support the IParsable
and ISpanParsable<TSelf>
interfaces because they contain static virtual members, a feature of C# 11.0 that cannot be polyfilled.
The methods are there, you can use them (for instance you can call DateOnly.Parse(str)
), but they are just public methods of the individual types, not associated with any interface.
Note #3: In projects referencing PolyKit.Embedded
and targeting .NET Framework or .NET Standard 2.0, method HashCode.AddBytes(ReadOnlySpan<byte>)
will only be compiled if package System.Memory
is also referenced.
Note #4: This is not, strictly speaking, a polyfill, but it spares you the trouble of defining your own internal ValidatedNotNullAttribute
or referencing Visual Studio SDK.
The attribute provided by PolyKit is in the PolyKit.Diagnostics.CodeAnalysis
namespace.
Note #5: Polyfilling StackTraceHiddenAttribute
would be worthless without providing actual support for it. PolyKit cannot replace relevant code (Exception.StackTrace
getter and StackTrace.ToString()
method) in frameworks prior to .NET 6.0.
PolyKit provides two extension methods, Exception.GetStackTraceHidingFrames()
and StackTrace.ToStringHidingFrames()
; these methods retrofit the behavior of Exception.StackTrace
and StackTrace.ToString()
respectively to frameworks prior to .NET 6, where StackTraceHiddenAttribute
was first introduced.
When used on .NET 6.0+, the above extension methods are just façades for their BCL counterparts.
Be aware of the following limitations:
- the output of the "retrofitted" extension methods is always in US English, regardless of any locale setting;
- external exception stack trace boundaries ("End of stack trace from previous location" lines) are missing from returned strings.
Note #6: Obviously PolyKit cannot extend System.Linq.Enumerable
, so we'll have to meet halfway on this.
PolyKit adds a System.Linq.PolyKitEnumerable
class, containing a TryGetCountWithoutEnumerating<TSource>
method that will just call TryGetNonEnumeratedCount<TSource>
on .NET 6.0+ and replicate most of its functionality (except where it requires access to runtime internal types) on older frameworks.
Note #7: PolyKit does not (and can not) add ISpanFormattable
support to .NET Runtime types: intVar is ISpanFormattable
will still be false
except on .NET 6.0 and later versions.
You can, however, implement ISpanFormattable
in a type exposed by a multi-target library, no #if
needed: it will behave just as you expect on .NET 6.0+, and still have a TryFormat
method on older platforms (unless you used an explicit implementation).
Also note that, in projects referencing PolyKit.Embedded
and targeting .NET Framework or .NET Standard 2.0, ISpanFormattable
will only be compiled if package System.Memory
is also referenced.
Note #8: RequiresDynamicCodeAttribute
was introduced with .NET 7.0 and requires .NET SDK 7.0 to be properly supported duting build.
- Ensure that all your target frameworks are supported by PolyKit.
- Add a package reference to
PolyKit
to your projects.
- Ensure that all your target frameworks are supported by PolyKit.
- Set the
LangVersion
property in your project toLatest
,Preview
, or at least 10. - Add a package reference to
PolyKit.Embedded
to your project.
Remember to setPrivateAssets="all"
if you add the package reference manually. - Add optional package references as needed (see notes #1 and #2 above).
- Ensure that all your target frameworks are supported by PolyKit.
- Set the
LangVersion
property in your project toLatest
,Preview
, or at least 10. - Add a package reference to
PolyKit.Embedded
to your library project.
Remember to setPrivateAssets="all"
if you add the package reference manually. - Add optional package references as needed (see notes #1 and #2 above).
- Set the
PolyKit_GeneratePublicTypes
property totrue
in your project file. - Add your own code to the library.
- You can now use your own library instead of
PolyKit
in your projects.
Of course we accept contributions! 😃 Just take a look at our Code of Conduct and Contributors guide, create your fork, and let's party! 🎉
Riccardo De Agostini |
||||||
Add your contributions |