| 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.csprojfile; - the meaning of
PackageReferencein the context of a.csprojfile; - 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,
internalpolyfills provided by thePolyKit.Embeddedpackage will suit just fine; - for a library whose public-facing APIs may require polyfills, for example if some
publicmethod accepts or returnsSpans,publicpolyfills provided by thePolyKitpackage 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
publicpolyfills provided by thePolyKitpackage; - if your solution contains a "core" library, referenced by all other projects, you may even spare an additional dependency by incorporating
publicpolyfills 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
GeneratedCodeattribute; - all added classes and structs are marked with
ExcludeFromCodeCoverageandDebuggerNonUserCodeattributes.
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 |
| 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 #3 |
StackTraceHidden attribute |
6.0 | Note #4 |
| support for writing custom string interpolation handlers | 6.0 | |
Enumerable.TryGetNonEnumeratedCount<TSource> method |
6.0 | Note #5 |
ISpanFormattable interface |
6.0 | Note #6 |
| trimming incompatibility attributes | 6.0 / 7.0 | Note #7 |
| 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, and System.HashCode, absent from .NET Framework (all versions) and .NET Standard 2.0.
If you reference the PolyKit.Embedded package in a project targeting .NET Framework or .NET Standard 2.0, you must also add a package reference to Microsoft.Bcl.HashCode;
for .NET Framewrok 4.6.2, a package reference to System.ValueTuple is also needed. Otherwise, compilation will not fail, but features dependent on HashCode and/or 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: 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 #4: 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 #5: 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 #6: 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 #7: 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
PolyKitto your projects.
- Ensure that all your target frameworks are supported by PolyKit.
- Set the
LangVersionproperty in your project toLatest,Preview, or at least 10. - Add a package reference to
PolyKit.Embeddedto 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
LangVersionproperty in your project toLatest,Preview, or at least 10. - Add a package reference to
PolyKit.Embeddedto 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_GeneratePublicTypesproperty totruein your project file. - Add your own code to the library.
- You can now use your own library instead of
PolyKitin 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 |
||||||
|
|
||||||
