Claude
Skills
Sign in
Back

dotnet-multi-targeting

Included with Lifetime
$97 forever

Targeting multiple TFMs or using newer C# on older TFMs. Polyfill strategy, API compat.

Backend & APIs

What this skill does


# dotnet-multi-targeting

Comprehensive guide for .NET multi-targeting strategies with a polyfill-first approach. This skill consumes the structured output from [skill:dotnet-version-detection] (TFM, C# version, preview flags) and provides actionable guidance on backporting language features, handling runtime gaps, and validating API compatibility across target frameworks.

**Out of scope:** TFM detection logic (owned by [skill:dotnet-version-detection]), version upgrade lane selection (see [skill:dotnet-version-upgrade]), platform-specific UI frameworks (MAUI, Blazor), cloud deployment configuration.

Cross-references: [skill:dotnet-version-detection] for TFM resolution and version matrix, [skill:dotnet-version-upgrade] for upgrade lane guidance and migration strategies.

---

## Decision Matrix: Polyfill vs Conditional Compilation

Use this matrix to select the correct strategy for each type of gap between your highest and lowest TFMs.

| Gap Type | Strategy | When to Use | Example |
|----------|----------|-------------|---------|
| Language/syntax feature | Polyfill (PolySharp) | Compiler needs attribute/type stubs to emit newer syntax on older TFMs | `required` modifier, `init` properties, `SetsRequiredMembers` on net8.0 |
| BCL API addition | Polyfill (SimonCropp/Polyfill) if available, else `#if` | A newer BCL type or method is missing on older TFMs | `System.Threading.Lock` on net8.0, `Index`/`Range` on netstandard2.0 |
| Runtime behavior difference | Conditional compilation (`#if`) or adapter pattern | Behavior differs at runtime regardless of compilation | Runtime-async (net11.0 only), different GC modes, `SearchValues<T>` runtime optimizations |
| Platform API divergence | Conditional compilation with `[SupportedOSPlatform]` | API exists only on specific OS targets | Windows Registry APIs, Android-specific intents, iOS keychain |

**Decision flow:**
1. Can a compile-time polyfill satisfy the gap? Use PolySharp or SimonCropp/Polyfill.
2. Is the gap a missing BCL API with no polyfill available? Use `#if` with TFM-specific code.
3. Is the gap a runtime behavior difference? Use `#if` or the adapter pattern to isolate divergent code paths.
4. Is the gap platform-specific? Use `#if` with `[SupportedOSPlatform]` attributes.

---

## PolySharp (Compiler-Synthesized Polyfills)

PolySharp is a source generator that synthesizes the attribute and type stubs the C# compiler needs to emit newer language features when targeting older TFMs. It operates entirely at compile time -- no runtime dependencies are added.

### What PolySharp Provides

- `required` modifier support (C# 11+)
- `init` property accessors (C# 9+)
- `SetsRequiredMembers` attribute
- `CompilerFeatureRequired` attribute
- `IsExternalInit` type
- `CallerArgumentExpression` attribute
- `StackTraceHidden` attribute
- `UnscopedRef` attribute
- `InterpolatedStringHandler` attributes
- `ModuleInitializer` attribute
- Index and Range support types

### Setup

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net8.0;net10.0</TargetFrameworks>
    <!-- Use the highest C# version across all TFMs -->
    <LangVersion>14</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <!-- PolySharp is a source generator; it adds no runtime dependency -->
    <PackageReference Include="PolySharp" Version="1.*">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>
  </ItemGroup>
</Project>
```

### How It Works

PolySharp detects which polyfill types are missing for the current TFM and generates source for only those types. On net10.0, where `required` is natively supported, the generator emits nothing -- zero overhead.

```csharp
// This compiles on net8.0 WITH PolySharp installed,
// because PolySharp generates the required CompilerFeatureRequired
// and IsExternalInit types that the compiler needs.
public class UserProfile
{
    public required string DisplayName { get; init; }
    public required string Email { get; init; }
    public string? Bio { get; set; }
}
```

### PolySharp Limitations

- PolySharp provides **compiler stubs only**. It does not backport runtime behavior.
- Features that require runtime support (e.g., runtime-async, `SearchValues<T>` hardware acceleration) cannot be polyfilled.
- If a feature needs both a compiler attribute AND a BCL API (e.g., collection expressions with `Span<T>` overloads), you may need both PolySharp and SimonCropp/Polyfill.

---

## SimonCropp/Polyfill (BCL API Polyfills)

SimonCropp/Polyfill provides source-generated implementations of newer BCL APIs for older TFMs. Unlike PolySharp (which provides compiler attribute stubs), Polyfill provides actual method and type implementations.

### What Polyfill Provides

Key polyfilled APIs (non-exhaustive):

- `System.Threading.Lock` (C# 13 / net9.0+)
- `String.Contains(char)`, `String.Contains(string, StringComparison)`
- `String.ReplaceLineEndings()`
- `HashCode` struct
- `SkipLocalsInit` attribute
- `TaskCompletionSource` (non-generic)
- `Stream.ReadExactly`, `Stream.ReadAtLeast`
- `Memory<T>` and `Span<T>` extensions
- `IReadOnlySet<T>` interface
- Various LINQ additions (`TryGetNonEnumeratedCount`, `DistinctBy`, `Chunk`, etc.)

### Setup

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net8.0;net10.0</TargetFrameworks>
    <LangVersion>14</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <!-- Polyfill is a source generator; no runtime dependency -->
    <PackageReference Include="Polyfill" Version="7.*">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>
  </ItemGroup>
</Project>
```

### Usage Example

```csharp
// System.Threading.Lock is a net9.0+ type.
// With Polyfill installed, this compiles on net8.0.
public class ThrottledProcessor
{
    private readonly Lock _lock = new();

    public void Process(string item)
    {
        lock (_lock)
        {
            // Lock provides better diagnostics than object-based locking
            Console.WriteLine($"Processing: {item}");
        }
    }
}
```

### Combining PolySharp and Polyfill

For maximum compatibility, use both packages together. They are complementary and do not conflict:

```xml
<ItemGroup>
  <!-- PolySharp: compiler attribute stubs (required, init, etc.) -->
  <PackageReference Include="PolySharp" Version="1.*">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
  </PackageReference>

  <!-- Polyfill: BCL API implementations (Lock, LINQ additions, etc.) -->
  <PackageReference Include="Polyfill" Version="7.*">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
  </PackageReference>
</ItemGroup>
```

With both installed, you get full language feature support (PolySharp) **and** BCL API backporting (Polyfill) on older TFMs.

---

## Conditional Compilation

Use conditional compilation (`#if`) when the gap is a runtime behavior difference or a platform API that cannot be polyfilled at compile time.

### TFM-Based Conditionals

The compiler defines preprocessor symbols for each TFM. Use `NET8_0_OR_GREATER`-style symbols (available since .NET 5) for version range checks:

```csharp
public static class PerformanceHelper
{
#if NET10_0_OR_GREATER
    // net10.0+ has optimized SearchValues with hardware acceleration
    private static readonly SearchValues<char> s_vowels =
        SearchValues.Create("aeiouAEIOU");

    public static int CountVowels(ReadOnlySpan<char> text)
        => text.Count(s_vowels);
#else
    // Fallback for net8.0: manual loop
    public static int CountVowels(ReadOnlySpan<char> text)
    {
        int count = 0;
        foreach (char c in text)
        {
            if ("aeiouAEIOU".Contains(c))
                count++;
        }
       

Related in Backend & APIs