This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Polyfill is a source-only NuGet package that exposes newer .NET and C# features to older runtimes. It ships as C# source files (not compiled assemblies) that get compiled directly into consuming projects. There are 723+ polyfilled APIs. A compiled library variant exists as PolyfillLib.
Targets netstandard2.0 and supports: net461–net481, netcoreapp2.0–3.1, net5.0–net11.0, uap10.
# Build the full solution
dotnet build src/Polyfill.slnx --configuration Release
# Build just the core projects (faster iteration)
dotnet build src/Polyfill.slnf --configuration Release
# Run all tests (requires build first)
pwsh ./src/run-tests.ps1
# Run a single test project for a specific framework
dotnet run --project src/Tests/Tests.csproj --configuration Release --framework net10.0 --no-build
# Run a single test by name (TUnit uses Microsoft.Testing.Platform)
# Use --treenode-filter (not --filter). * matches within a segment, ** only at the end.
# Pattern: /assembly/namespace/ClassName/MethodName
dotnet run --project src/Tests/Tests.csproj --framework net10.0 -- --treenode-filter "/*/*/PolyfillTests/StreamReaderReadAsync"
# Run all tests matching a prefix within a class
dotnet run --project src/Tests/Tests.csproj --framework net10.0 -- --treenode-filter "/*/*/PolyfillTests/Directory_*"Tests use TUnit (not xUnit/NUnit). Test assertions use await Assert.That(...) syntax.
The NuGet package ships .cs files (not a DLL). The Polyfill.nuspec packs files from src/Split/ into contentFiles/cs/. The Polyfill.targets file is included in the package to set up conditional compilation constants at build time based on the consumer's target framework and referenced packages.
src/Polyfill/— The canonical source. Contains all polyfill implementations. This is the "master" copy.src/Split/{tfm}/— Per-target-framework copies of source files that ship in the NuGet. These are generated/maintained byApiBuilderTests— do not edit directly.src/Tests/— Primary TUnit test project, multi-targets many frameworks.src/Consume/— Compilation-only project that ensures all APIs compile on all supported frameworks (no test assertions).src/Consume*/— AllConsume*projects get their polyfill source files via<Import Project="$(SolutionDir)\TestIncludes.targets" />, which includesSplit/{tfm}/**/*.csbased on the target framework. They have no polyfill.csfiles of their own — the Split files ARE their compiled source.src/ApiBuilderTests/— Tests that generate the Split output and verify API surface.
Polyfill uses extensive #if directives. Key constants:
- Framework constants:
NETFRAMEWORK,NETSTANDARD,NETCOREAPP2X,NETCOREAPP3X,NET46X,NET47X,NET48X - Feature constants (set by
Polyfill.targetsbased on referenced packages):FeatureMemory,FeatureValueTask,FeatureValueTuple,FeatureRuntimeInformation,FeatureHttp,FeatureCompression,FeatureAsyncInterfaces - Consumer options:
PolyPublic(makes APIs public),PolyUseEmbeddedAttribute,PolyEnsure,PolyGuard,PolyNullability,PolyArgumentExceptions,PolyStringInterpolation AllowUnsafeBlocks— enables unsafe polyfill variants for better performance
- Attributes (e.g.,
ModuleInitializerAttribute.cs): standalone files, one per attribute - Extension methods on existing types:
Polyfill_{TypeName}.cs(e.g.,Polyfill_StreamWriter.cs) - Static helper polyfills (add a static method to an existing type via
extension(Type)):{TypeName}Polyfill.cs(e.g.,EnumPolyfill.cs) - Recreated BCL types (a type that doesn't exist at all on older TFMs, e.g.
Base64Url,KeyValuePair,Lock,Index): standalone file{TypeName}.csdeclaring the type in its real namespace (not thePolyfillpartial), wrapped#if !NETx_0_OR_GREATER...#else [assembly: TypeForwardedTo(typeof(...))] #endif. Apply[ExcludeFromCodeCoverage],[DebuggerNonUserCode], and thePolyUseEmbeddedAttribute/PolyPublicgates. - Optional feature groups:
Ensure/,Guard/,Nullability/,ArgumentExceptions/,StringInterpolation/
Important constraints:
- Each
*Polyfill.csfile must contain exactly one top-level type (thePolyfillpartial class). Helper classes must be nested insidePolyfill, otherwiseReadMethodsForFilesinBuildApiTestwill throw. - The filename of static polyfill files directly determines the
api_list.include.mdsection header:{TypeName}Polyfill.cs→#### {TypeName}. For example,FilePolyfill.cs→#### File. Choose filenames to match the type being extended. //Link:comments on public methods must use?view=net-11.0for learn.microsoft.com URLs (enforced byLinkReader). For overloaded methods, include the#fragmentanchor pointing to the specific overload (e.g.,#system-type-method(system-string-system-int32)).//Note: <text>comments (also leading trivia, parsed byLinkReader.GetNotes) attach a caveat to a polyfilled API. Each line emits a sub-bullet* Note: <text>under the signature inapi_list.include.md(rendered byBuildApiTest.WriteNotes). Use sparingly — only when behavior diverges from the BCL in a way callers must know about (e.g., O(n) vs O(1), free-standing struct vs nested type, different exception type). Don't restate the doc summary.- Section-level opt-in gates (e.g.,
Ensure,ArgumentNullException) are emitted as a> Requires <PolyXxx>true</PolyXxx>blockquote under the section header. The mapping lives inBuildApiTest.sectionGates— add an entry there when you introduce a new section whose members all require an MSBuild flag. - Recreated-type files (
{TypeName}.cs) are not auto-discovered by the API-list generator (only*Polyfill.csis). Register each one inBuildApiTest.RunWithRoslynviaWriteHelper("{TypeName}", ...)(lists its members) orWriteType("{TypeName}", ...)(header only, counts as 1). extension(Type)only compiles whereTypealready exists, so it can't introduce a brand-new type — recreate the type instead. When a type's members were added across different .NET versions (e.g.CollectionsMarshal: type +AsSpanin net5,SetCountin net8), you may need both a recreated type (window where the type is absent) and anextension(Type)(window where the type exists but the member doesn't). Keep them in separate files:Identifiers.ReadTypesForFileparses each file under every moniker, so a single file that resolves to different top-level types across monikers (e.g.CollectionsMarshalvsPolyfill) makesReadMethodsForFilesthrow.- Deliverability over perf: if an API's only benefit is a performance optimization (e.g.
List.EnsureCapacity/TrimExcess), polyfilling it as a no-op (or a simpler correct-but-slower implementation) is acceptable — document the divergence with//Note:. The bar is that consumer code compiles and stays behaviorally correct; matching the BCL's performance characteristics is secondary.
- Tests — Main tests, verifies all APIs
- NoRefsTests — Tests subset that works without optional NuGet references
- PublicTests — Tests with
PolyPublic=true - UnsafeTests — Tests with
AllowUnsafeBlocks=true - EmbeddedTests — Tests with
PolyUseEmbeddedAttribute=true - NoExtrasTests — Tests without optional feature groups enabled
- Add implementation to
src/Polyfill/with#ifguards for frameworks that need it - Use
#pragma warning disableat the top - Use
#if PolyPublic/public/#endifpattern for type visibility - Use
#if PolyUseEmbeddedAttribute/[global::Microsoft.CodeAnalysis.EmbeddedAttribute]/#endifon the type — this prevents conflicts between source-included and compiled (PolyfillLib) variants inEmbeddedTests - Add test to
src/Tests/PolyfillTests_{TypeName}.cs - Add compilation usage to
src/Consume/Consume.cs - Run
ApiBuilderTestsin Debug to regenerate Split files andapi_list.include.md:Thedotnet run --project src/ApiBuilderTests/ApiBuilderTests.csproj --configuration Debug
Splitter.RunandRunWithRoslyntests are[Explicit]in Release mode, so they only execute in Debug.
Test #if guard rules: Tests should run on all target frameworks, not just the ones where the polyfill is active. On older frameworks the test exercises the polyfill; on newer frameworks it exercises the real BCL method. This validates that the polyfill behavior matches the native implementation. Do not use framework-excluding guards like #if !NETx_0_OR_GREATER in tests. Only use feature guards (#if FeatureMemory, #if FeatureAsyncInterfaces, etc.) when the test code requires types/APIs from those feature packages to compile. Note: Span<T>/ReadOnlySpan<T> are ref structs and can't live across an await; copy the values you need into locals before await Assert.That(...).
- SDK: .NET 11.0 (
src/global.json) - LangVersion: 14.0
- Central Package Management:
src/Directory.Packages.props TreatWarningsAsErrorsis enabledImplicitUsingsis disabled — allusingstatements must be explicitNullableis enabled- Line endings: LF (
\n) — do not use CRLF - CI: AppVeyor (Windows), builds solution then runs
run-tests.ps1