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.
Scope
- Decision matrix: polyfill vs conditional compilation
- PolySharp for compiler-synthesized polyfills
- SimonCropp/Polyfill for BCL API backporting
- Conditional compilation with TFM-based preprocessor symbols
- Multi-targeting .csproj patterns and TFM-specific source files
- API compatibility validation (EnablePackageValidation, ApiCompat tool)
Out of scope
- TFM detection logic -- see [skill:dotnet-version-detection]
- Version upgrade lane selection -- see [skill:dotnet-version-upgrade]
- Platform-specific UI frameworks (MAUI, Blazor) -- see respective framework skills
- 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:
- Can a compile-time polyfill satisfy the gap? Use PolySharp or SimonCropp/Polyfill.
- Is the gap a missing BCL API with no polyfill available? Use
#if with TFM-specific code.
- Is the gap a runtime behavior difference? Use
#if or the adapter pattern to isolate divergent code paths.
- 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
<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>
```text
### 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; }
}
```text
### 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>
```text
### 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}");
}
}
}
```text
### 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>
```text
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++;
}
return count;
}
#endif
}
```text
### Available Preprocessor Symbols
| Symbol | True When |
|--------|-----------|
| `NET8_0` | Exactly net8.0 |
| `NET8_0_OR_GREATER` | net8.0 or any higher version |
| `NET9_0_OR_GREATER` | net9.0 or any higher version |
| `NET10_0_OR_GREATER` | net10.0 or any higher version |
| `NET11_0_OR_GREATER` | net11.0 or any higher version |
| `NETSTANDARD2_0` | Exactly netstandard2.0 |
| `NETSTANDARD2_0_OR_GREATER` | netstandard2.0 or higher |
### When #if Is Correct
1. **Runtime behavior gap:** The API exists on both TFMs but behaves differently at runtime (e.g., `GC.Collect` modes, `HttpClient` connection pooling behavior).
2. **No polyfill available:** The BCL API is not covered by SimonCropp/Polyfill and cannot be stubbed.
3. **Performance-critical path:** You want to use a TFM-specific optimized API path (e.g., `SearchValues<T>`, `FrozenDictionary<K,V>`).
4. **Platform API:** The API is available only on a specific OS platform target.
### When #if Is Wrong
- **Language syntax feature** (e.g., `required`, `init`): Use PolySharp instead.
- **Missing BCL method** that has a polyfill (e.g., `System.Threading.Lock`): Use SimonCropp/Polyfill instead.
- Wrapping entire files in `#if` blocks -- use TFM-specific source files instead (see below).
---
## Multi-Targeting .csproj Patterns
### Basic Multi-Targeting
```xml
<PropertyGroup>
<!-- Semicolon-delimited list of TFMs -->
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<!-- Use the highest C# version to access all language features -->
<LangVersion>14</LangVersion>
</PropertyGroup>
```csharp
### Conditional Package References
Some packages are only needed on specific TFMs:
```xml
<ItemGroup>
<!-- Polyfill packages: only needed on older TFMs, but safe to reference
unconditionally because they emit nothing when features are native -->
<PackageReference Include="PolySharp" Version="1.*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<!-- TFM-conditional package: only available/needed on specific TFMs -->
<PackageReference Include="System.Text.Json" Version="9.*"
Condition="'$(TargetFramework)' == 'net8.0'" />
</ItemGroup>
```json
### TFM-Specific Source Files
For large blocks of TFM-specific code, use dedicated source files instead of `#if` blocks:
```xml
<ItemGroup>
<!-- SDK-style projects auto-include all *.cs files. Remove TFM-specific
directories first to avoid NETSDK1022 duplicate compile items. -->
<Compile Remove="Compatibility\**\*.cs" />
<!-- Then conditionally include only the files for the current TFM -->
<Compile Include="Compatibility\Net8\**\*.cs"
Condition="'$(TargetFramework)' == 'net8.0'" />
<Compile Include="Compatibility\Net10\**\*.cs"
Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net10.0'))" />
</ItemGroup>
```csharp
Directory structure:
```text
MyLibrary/
Compatibility/
Net8/
SearchValuesCompat.cs
Net10/
SearchValuesNative.cs
Services/
TextAnalyzer.cs # shared code, references interface
MyLibrary.csproj
```csharp
### Platform-Specific TFMs
For projects targeting platform-specific TFMs (MAUI, Uno):
```xml
<PropertyGroup>
<!-- Use version-agnostic platform globs where possible -->
<TargetFrameworks>net10.0;net10.0-android;net10.0-ios;net10.0-windows10.0.19041.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.*" />
</ItemGroup>
```text
### Shared Properties via Directory.Build.props
For multi-project solutions, centralize multi-targeting configuration:
```xml
<!-- Directory.Build.props -->
<Project>
<PropertyGroup>
<LangVersion>14</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PolySharp" Version="1.*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Polyfill" Version="7.*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
## Detailed Examples
See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.
1---2name: dotnet-multi-targeting3category: developer-experience4subcategory: project5description: Targets multiple TFMs via polyfills and conditional compilation. PolySharp, API compat.6license: MIT7targets: ['*']8tags: [foundation, dotnet, skill]9version: '0.0.1'10author: 'dotnet-agent-harness'11invocable: true12claudecode:13 allowed-tools: ['Read', 'Grep', 'Glob', 'Bash', 'Write', 'Edit']14codexcli:15 short-description: '.NET skill guidance for foundation tasks'16opencode:17 allowed-tools: ['Read', 'Grep', 'Glob', 'Bash', 'Write', 'Edit']18copilot: {}19geminicli: {}20antigravity: {}21---2223# dotnet-multi-targeting2425Comprehensive guide for .NET multi-targeting strategies with a polyfill-first approach. This skill consumes the26structured output from [skill:dotnet-version-detection] (TFM, C# version, preview flags) and provides actionable27guidance on backporting language features, handling runtime gaps, and validating API compatibility across target28frameworks.2930## Scope3132- Decision matrix: polyfill vs conditional compilation33- PolySharp for compiler-synthesized polyfills34- SimonCropp/Polyfill for BCL API backporting35- Conditional compilation with TFM-based preprocessor symbols36- Multi-targeting .csproj patterns and TFM-specific source files37- API compatibility validation (EnablePackageValidation, ApiCompat tool)3839## Out of scope4041- TFM detection logic -- see [skill:dotnet-version-detection]42- Version upgrade lane selection -- see [skill:dotnet-version-upgrade]43- Platform-specific UI frameworks (MAUI, Blazor) -- see respective framework skills44- Cloud deployment configuration4546Cross-references: [skill:dotnet-version-detection] for TFM resolution and version matrix, [skill:dotnet-version-upgrade]47for upgrade lane guidance and migration strategies.4849---5051## Decision Matrix: Polyfill vs Conditional Compilation5253Use this matrix to select the correct strategy for each type of gap between your highest and lowest TFMs.5455| Gap Type | Strategy | When to Use | Example |56| --------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |57| 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 |58| 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 |59| 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 |60| Platform API divergence | Conditional compilation with `[SupportedOSPlatform]` | API exists only on specific OS targets | Windows Registry APIs, Android-specific intents, iOS keychain |6162**Decision flow:**63641. Can a compile-time polyfill satisfy the gap? Use PolySharp or SimonCropp/Polyfill.652. Is the gap a missing BCL API with no polyfill available? Use `#if` with TFM-specific code.663. Is the gap a runtime behavior difference? Use `#if` or the adapter pattern to isolate divergent code paths.674. Is the gap platform-specific? Use `#if` with `[SupportedOSPlatform]` attributes.6869---7071## PolySharp (Compiler-Synthesized Polyfills)7273PolySharp is a source generator that synthesizes the attribute and type stubs the C# compiler needs to emit newer74language features when targeting older TFMs. It operates entirely at compile time -- no runtime dependencies are added.7576### What PolySharp Provides7778- `required` modifier support (C# 11+)79- `init` property accessors (C# 9+)80- `SetsRequiredMembers` attribute81- `CompilerFeatureRequired` attribute82- `IsExternalInit` type83- `CallerArgumentExpression` attribute84- `StackTraceHidden` attribute85- `UnscopedRef` attribute86- `InterpolatedStringHandler` attributes87- `ModuleInitializer` attribute88- Index and Range support types8990### Setup9192````xml9394<Project Sdk="Microsoft.NET.Sdk">95 <PropertyGroup>96 <TargetFrameworks>net8.0;net10.0</TargetFrameworks>97 <!-- Use the highest C# version across all TFMs -->98 <LangVersion>14</LangVersion>99 </PropertyGroup>100101 <ItemGroup>102 <!-- PolySharp is a source generator; it adds no runtime dependency -->103 <PackageReference Include="PolySharp" Version="1.*">104 <PrivateAssets>all</PrivateAssets>105 <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>106 </PackageReference>107 </ItemGroup>108</Project>109110```text111112### How It Works113114PolySharp 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.115116```csharp117118// This compiles on net8.0 WITH PolySharp installed,119// because PolySharp generates the required CompilerFeatureRequired120// and IsExternalInit types that the compiler needs.121public class UserProfile122{123 public required string DisplayName { get; init; }124 public required string Email { get; init; }125 public string? Bio { get; set; }126}127128```text129130### PolySharp Limitations131132- PolySharp provides **compiler stubs only**. It does not backport runtime behavior.133- Features that require runtime support (e.g., runtime-async, `SearchValues<T>` hardware acceleration) cannot be polyfilled.134- 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.135136---137138## SimonCropp/Polyfill (BCL API Polyfills)139140SimonCropp/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.141142### What Polyfill Provides143144Key polyfilled APIs (non-exhaustive):145146- `System.Threading.Lock` (C# 13 / net9.0+)147- `String.Contains(char)`, `String.Contains(string, StringComparison)`148- `String.ReplaceLineEndings()`149- `HashCode` struct150- `SkipLocalsInit` attribute151- `TaskCompletionSource` (non-generic)152- `Stream.ReadExactly`, `Stream.ReadAtLeast`153- `Memory<T>` and `Span<T>` extensions154- `IReadOnlySet<T>` interface155- Various LINQ additions (`TryGetNonEnumeratedCount`, `DistinctBy`, `Chunk`, etc.)156157### Setup158159```xml160161<Project Sdk="Microsoft.NET.Sdk">162 <PropertyGroup>163 <TargetFrameworks>net8.0;net10.0</TargetFrameworks>164 <LangVersion>14</LangVersion>165 </PropertyGroup>166167 <ItemGroup>168 <!-- Polyfill is a source generator; no runtime dependency -->169 <PackageReference Include="Polyfill" Version="7.*">170 <PrivateAssets>all</PrivateAssets>171 <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>172 </PackageReference>173 </ItemGroup>174</Project>175176```text177178### Usage Example179180```csharp181182// System.Threading.Lock is a net9.0+ type.183// With Polyfill installed, this compiles on net8.0.184public class ThrottledProcessor185{186 private readonly Lock _lock = new();187188 public void Process(string item)189 {190 lock (_lock)191 {192 // Lock provides better diagnostics than object-based locking193 Console.WriteLine($"Processing: {item}");194 }195 }196}197198```text199200### Combining PolySharp and Polyfill201202For maximum compatibility, use both packages together. They are complementary and do not conflict:203204```xml205206<ItemGroup>207 <!-- PolySharp: compiler attribute stubs (required, init, etc.) -->208 <PackageReference Include="PolySharp" Version="1.*">209 <PrivateAssets>all</PrivateAssets>210 <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>211 </PackageReference>212213 <!-- Polyfill: BCL API implementations (Lock, LINQ additions, etc.) -->214 <PackageReference Include="Polyfill" Version="7.*">215 <PrivateAssets>all</PrivateAssets>216 <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>217 </PackageReference>218</ItemGroup>219220```text221222With both installed, you get full language feature support (PolySharp) **and** BCL API backporting (Polyfill) on older TFMs.223224---225226## Conditional Compilation227228Use conditional compilation (`#if`) when the gap is a runtime behavior difference or a platform API that cannot be polyfilled at compile time.229230### TFM-Based Conditionals231232The compiler defines preprocessor symbols for each TFM. Use `NET8_0_OR_GREATER`-style symbols (available since .NET 5) for version range checks:233234```csharp235236public static class PerformanceHelper237{238#if NET10_0_OR_GREATER239 // net10.0+ has optimized SearchValues with hardware acceleration240 private static readonly SearchValues<char> s_vowels =241 SearchValues.Create("aeiouAEIOU");242243 public static int CountVowels(ReadOnlySpan<char> text)244 => text.Count(s_vowels);245#else246 // Fallback for net8.0: manual loop247 public static int CountVowels(ReadOnlySpan<char> text)248 {249 int count = 0;250 foreach (char c in text)251 {252 if ("aeiouAEIOU".Contains(c))253 count++;254 }255 return count;256 }257#endif258}259260```text261262### Available Preprocessor Symbols263264| Symbol | True When |265|--------|-----------|266| `NET8_0` | Exactly net8.0 |267| `NET8_0_OR_GREATER` | net8.0 or any higher version |268| `NET9_0_OR_GREATER` | net9.0 or any higher version |269| `NET10_0_OR_GREATER` | net10.0 or any higher version |270| `NET11_0_OR_GREATER` | net11.0 or any higher version |271| `NETSTANDARD2_0` | Exactly netstandard2.0 |272| `NETSTANDARD2_0_OR_GREATER` | netstandard2.0 or higher |273274### When #if Is Correct2752761. **Runtime behavior gap:** The API exists on both TFMs but behaves differently at runtime (e.g., `GC.Collect` modes, `HttpClient` connection pooling behavior).2772. **No polyfill available:** The BCL API is not covered by SimonCropp/Polyfill and cannot be stubbed.2783. **Performance-critical path:** You want to use a TFM-specific optimized API path (e.g., `SearchValues<T>`, `FrozenDictionary<K,V>`).2794. **Platform API:** The API is available only on a specific OS platform target.280281### When #if Is Wrong282283- **Language syntax feature** (e.g., `required`, `init`): Use PolySharp instead.284- **Missing BCL method** that has a polyfill (e.g., `System.Threading.Lock`): Use SimonCropp/Polyfill instead.285- Wrapping entire files in `#if` blocks -- use TFM-specific source files instead (see below).286287---288289## Multi-Targeting .csproj Patterns290291### Basic Multi-Targeting292293```xml294295<PropertyGroup>296 <!-- Semicolon-delimited list of TFMs -->297 <TargetFrameworks>net8.0;net10.0</TargetFrameworks>298 <!-- Use the highest C# version to access all language features -->299 <LangVersion>14</LangVersion>300</PropertyGroup>301302```csharp303304### Conditional Package References305306Some packages are only needed on specific TFMs:307308```xml309310<ItemGroup>311 <!-- Polyfill packages: only needed on older TFMs, but safe to reference312 unconditionally because they emit nothing when features are native -->313 <PackageReference Include="PolySharp" Version="1.*">314 <PrivateAssets>all</PrivateAssets>315 <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>316 </PackageReference>317318 <!-- TFM-conditional package: only available/needed on specific TFMs -->319 <PackageReference Include="System.Text.Json" Version="9.*"320 Condition="'$(TargetFramework)' == 'net8.0'" />321</ItemGroup>322323```json324325### TFM-Specific Source Files326327For large blocks of TFM-specific code, use dedicated source files instead of `#if` blocks:328329```xml330331<ItemGroup>332 <!-- SDK-style projects auto-include all *.cs files. Remove TFM-specific333 directories first to avoid NETSDK1022 duplicate compile items. -->334 <Compile Remove="Compatibility\**\*.cs" />335336 <!-- Then conditionally include only the files for the current TFM -->337 <Compile Include="Compatibility\Net8\**\*.cs"338 Condition="'$(TargetFramework)' == 'net8.0'" />339 <Compile Include="Compatibility\Net10\**\*.cs"340 Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net10.0'))" />341</ItemGroup>342343```csharp344345Directory structure:346347```text348349MyLibrary/350 Compatibility/351 Net8/352 SearchValuesCompat.cs353 Net10/354 SearchValuesNative.cs355 Services/356 TextAnalyzer.cs # shared code, references interface357 MyLibrary.csproj358359```csharp360361### Platform-Specific TFMs362363For projects targeting platform-specific TFMs (MAUI, Uno):364365```xml366367<PropertyGroup>368 <!-- Use version-agnostic platform globs where possible -->369 <TargetFrameworks>net10.0;net10.0-android;net10.0-ios;net10.0-windows10.0.19041.0</TargetFrameworks>370</PropertyGroup>371372<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">373 <PackageReference Include="Xamarin.AndroidX.Core" Version="1.*" />374</ItemGroup>375376```text377378### Shared Properties via Directory.Build.props379380For multi-project solutions, centralize multi-targeting configuration:381382```xml383384<!-- Directory.Build.props -->385<Project>386 <PropertyGroup>387 <LangVersion>14</LangVersion>388 </PropertyGroup>389390 <ItemGroup>391 <PackageReference Include="PolySharp" Version="1.*">392 <PrivateAssets>all</PrivateAssets>393 <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>394 </PackageReference>395 <PackageReference Include="Polyfill" Version="7.*">396 <PrivateAssets>all</PrivateAssets>397 <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>398 </PackageReference>399 </ItemGroup>400</Project>401402## Detailed Examples403404See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.