Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .vale.ini
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Microsoft.Accessibility = NO
Microsoft.Passive = NO
Microsoft.Suspended = NO
Microsoft.Vocab = NO
Microsoft.Spacing = NO
Microsoft.Dashes = NO

# Not relevant for Fern audience
Microsoft.GeneralURL = NO
Expand Down
2 changes: 2 additions & 0 deletions .vale/styles/FernStyles/Acronyms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,6 @@ exceptions:
- MDX # Fern-specific
- SEO # Fern-specific
- AWS # Fern-specific
- TLS # Fern-specific
- BCL # Fern-specific
- LLM # Fern-specific
6 changes: 5 additions & 1 deletion .vale/styles/FernStyles/Headings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ exceptions:
- Support
- Markdown
- Docs
- Mono
- Unity
- SDK
- SDKs
- API Reference
- API Explorer
- GitHub
- README
- Action
- Actions
- Actions
271 changes: 271 additions & 0 deletions fern/products/sdks/overview/csharp/version-compatibility.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
---
title: Using modern C# with older .NET frameworks
description: Learn how to use modern C# features when targeting older .NET Framework versions (net462, net472, net48) and Unity.
---

You can target older .NET Framework versions (`net462`, `net472`, `net48`) and Unity while using the latest C# language features. This requires a modern Roslyn compiler and the appropriate reference assemblies.

## Overview

The C# language version is determined by your compiler, while the target framework determines which runtime and base class library APIs are available.

To compile modern C# (9+) for older .NET Framework targets, you need:
- A compiler that supports that C# version
- The appropriate .NET Framework Developer Pack or reference assemblies

<AccordionGroup>
<Accordion title="Target framework support">

| Target | Minimum Windows | Notes |
|--------|------------------|------------|
| **net462** | Windows 7 SP1 / Server 2008 R2 | Oldest supported for modern builds. Limited TLS support, older BCL. |
| **net472** | Windows 7 SP1 / Server 2008 R2 | Broad compatibility, supported for most enterprise apps. |
| **net48** | Windows 7 SP1 / Server 2008 R2 | Latest .NET Framework version with best long-term support. |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows version and Notes are TMI IMO

</Accordion>
<Accordion title="C# language feature caveats">

Most modern C# features work on `net462`, `net472`, and `net48`, but a few require workarounds:

- **`init` accessors** – Requires the `System.Runtime.CompilerServices.IsExternalInit` type. Add a small shim in your project or reference a helper package.
- **Records** - Work out of the box (compiler-generated IL), but may need the same `IsExternalInit` shim.
- **Nullable annotations** - Purely compile-time, work without modification.
</Accordion>
</AccordionGroup>

## IDE compatibility

All major IDEs support modern C# when targeting `net462`, `net472`, and `net48` versions:

- **Visual Studio 2022** (recommended) - Works out of the box. Set `<LangVersion>` in your project and install the appropriate [developer pack](#developer-packs).
- **Visual Studio 2019** - Supports C# 9 natively. For newer C# versions, add the `Microsoft.Net.Compilers.Toolset` package.
- **Rider** - Uses Roslyn. Set `<LangVersion>` and ensure reference assemblies are available.
- **VS Code** - Works via OmniSharp and .NET SDK builds.
- **Unity** - Uses Unity's own C# compiler. See [Using SDKs with Unity](#using-sdks-with-unity).

<Info>
You can "bring your own compiler" with the `Microsoft.Net.Compilers.Toolset` package on any environment.
</Info>

<Accordion title="Developer packs">

- [.NET Framework 4.6.2 Developer Pack](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net462)
- [.NET Framework 4.7.2 Developer Pack](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net472)
- [.NET Framework 4.8 Developer Pack](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net48)
</Accordion>

## Compiler compatibility

Use the **Roslyn compiler** (`csc`) that comes with Visual Studio 2022+ or the .NET SDK. Set your C# version via `<LangVersion>` to `latest`, `preview`, or a specific version (minimum `9`).

Fern's .NET SDK doesn't support the legacy Mono compiler (`mcs`, `gmcs`). Use Roslyn even when building on Mono.

### Building on Mono

Use the .NET SDK (`dotnet`) with the `Microsoft.NETFramework.ReferenceAssemblies` package to supply `net48` references on non-Windows. Use Roslyn via `dotnet build` or Mono's MSBuild.

```bash title="macOS/Linux"
dotnet build -c Release
```

## Project configuration examples

Use SDK-style projects where possible, as they provide modern compilers and simpler multi-targeting. If you have a classic project format, consider converting to SDK-style. The [legacy projects section](#legacy-projects) covers legacy setups that can't be converted.

<Note title="Building on macOS/Linux">
When building for `net48` on macOS/Linux, you won't have the Windows targeting packs. Add the reference assemblies package:

```xml title="YourProject.csproj"
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
</ItemGroup>
```
This provides the `net48` reference assemblies so Roslyn can compile for `net48` on any platform. You'll still need Mono or Wine to execute the result.
</Note>

<AccordionGroup>
<Accordion title="SDK-style projects (recommended)">

Targeting `net48` with modern C#

<Steps>
<Step title="Configure your project">

<Info title="Prerequisite (Windows)">
Install the [.NET Framework 4.8 Developer Pack](#developer-packs) so the `net48` reference assemblies are available to the compiler.
</Info>

```xml title="YourProject.csproj"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Target .NET Framework 4.8 -->
<TargetFramework>net48</TargetFramework>

<!-- Use a modern C# version -->
<LangVersion>latest</LangVersion>

<!-- Optional, but recommended for better warnings -->
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
```
</Step>

<Step title="Build your project">
```bash
dotnet build -c Release
```
</Step>

<Step title="Multi-target (optional)">
To support multiple frameworks, use `<TargetFrameworks>`:
{/* <!-- vale off --> */}
```xml title="YourProject.csproj"
<TargetFrameworks>net48;net8.0</TargetFrameworks>
```
{/* <!-- vale on --> */}
</Step>
</Steps>
</Accordion>
<Accordion title="Legacy projects">

For older MSBuild/Visual Studio versions, Visual Studio 2019, or legacy `packages.config` setups, add a modern Roslyn toolset and target the old framework.

<Steps>
<Step title="Install target packs">
Install the appropriate [.NET Framework Developer Pack](#developer-packs) so Visual Studio can find the reference assemblies.
</Step>

<Step title="Set language version">
```xml title="YourProject.csproj"
<PropertyGroup>
<!-- Unlock modern C# -->
<LangVersion>latest</LangVersion>
</PropertyGroup>
```
Or use a specific version: `9`, `10`, `11`, `12`, or `preview`.
</Step>
<Step title="Add modern compiler">

```xml title="YourProject.csproj"
<ItemGroup>
<PackageReference Include="Microsoft.Net.Compilers.Toolset" Version="x.y.z">
<!-- Keep it private: -->
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
```
This ensures your build uses a modern `csc.exe` even if your IDE/MSBuild is older.

</Step>
</Steps>
</Accordion>
</AccordionGroup>

## Distribution
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can get rid of this section. We don't need to teach users how to publish their own NuGet packages, just how to consume the package.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, removed


<AccordionGroup>
<Accordion title="Multi-target pack for NuGet">
A common configuration for broad compatibility across multiple .NET versions:

{/* <!-- vale off --> */}
```xml title="YourProject.csproj"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;net6.0;net8.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>Your.Package.Id</PackageId>
<Version>1.2.3</Version>
<Authors>Your Team</Authors>
<Description>Your description</Description>
</PropertyGroup>

<!-- Cross-platform net48 builds -->
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
</ItemGroup>
</Project>
```
{/* <!-- vale on --> */}

Build and pack:

```bash
dotnet pack -c Release
```
</Accordion>
<Accordion title="CI/CD configuration">
Force a specific C# version in your build pipeline:
```bash
dotnet build -c Release /p:LangVersion=11
```
</Accordion>
</AccordionGroup>

## Using SDKs with Unity

Unity controls its own C# compiler and .NET profile, which affects how you use Fern's .NET SDK.

<AccordionGroup>
<Accordion title="C# language version">
Unity compiles projects using Roslyn, typically supporting C# 9 (varies by Unity version). You can't force Unity to use a newer C# compiler by changing your package because Unity controls the compiler inside the Editor.
</Accordion>
<Accordion title="API compatibility level">
Configure this in Unity: `Edit → Project Settings → Player → Other Settings → Api Compatibility Level`

- **.NET Standard 2.1** (recommended for cross-platform plugins)
- **.NET Framework 4.x** (Unity's "4.x equivalent" profile)

</Accordion>
<Accordion title="Required assemblies">

Unity doesn't support NuGet packages, so you must manually download and add these assemblies to your Unity project:

| Assembly | NuGet Package |
|----------|---------------|
| Microsoft.Bcl.AsyncInterfaces | [10.0.0-preview.6.25358.103](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces/10.0.0-preview.6.25358.103) |
| OneOf | [3.0.271](https://www.nuget.org/packages/OneOf/3.0.271) |
| OneOf.Extended | [3.0.271](https://www.nuget.org/packages/OneOf.Extended/3.0.271) |
| System.Buffers | [4.6.1](https://www.nuget.org/packages/System.Buffers/4.6.1) |
| System.IO.Pipelines | [10.0.0-preview.6.25358.103](https://www.nuget.org/packages/System.IO.Pipelines/10.0.0-preview.6.25358.103) |
| System.Memory | [4.6.3](https://www.nuget.org/packages/System.Memory/4.6.3) |
| System.Runtime.CompilerServices.Unsafe | [6.1.2](https://www.nuget.org/packages/System.Runtime.CompilerServices.Unsafe/6.1.2) |
| System.Text.Encodings.Web | [10.0.0-preview.6.25358.103](https://www.nuget.org/packages/System.Text.Encodings.Web/10.0.0-preview.6.25358.103) |
| System.Text.Json | [10.0.0-preview.6.25358.103](https://www.nuget.org/packages/System.Text.Json/10.0.0-preview.6.25358.103) |
| System.Threading.Tasks.Extensions | [4.6.3](https://www.nuget.org/packages/System.Threading.Tasks.Extensions/4.6.3) |
| portable.system.datetimeonly | [9.0.0](https://www.nuget.org/packages/portable.system.datetimeonly/9.0.0) |

</Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
<Accordion title="Type or namespace IsExternalInit not found">
Add a small shim in your project or reference a helper package.
</Accordion>
<Accordion title="Reference assemblies for .NETFramework,Version=v4.8 were not found">
- Windows: install the **.NET Framework 4.8 Developer Pack**.
- Cross-platform/CI: add `Microsoft.NETFramework.ReferenceAssemblies` to the project (as `PrivateAssets=all`).
</Accordion>
<Accordion title="VS 2019 can't parse newer syntax">
Add `Microsoft.Net.Compilers.Toolset` to the project, or move to VS 2022+.
</Accordion>
<Accordion title="CS0619: 'Constructors of types with required members are not supported'">
This error occurs when using the `required` keyword with older Visual Studio versions. Follow the instructions at [Legacy projects](#legacy-projects) to add a modern compiler.

Check warning on line 256 in fern/products/sdks/overview/csharp/version-compatibility.mdx

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Microsoft.SentenceLength] Try to keep sentences short (< 30 words). Raw Output: {"message": "[Microsoft.SentenceLength] Try to keep sentences short (\u003c 30 words).", "location": {"path": "fern/products/sdks/overview/csharp/version-compatibility.mdx", "range": {"start": {"line": 256, "column": 88}}}, "severity": "INFO"}

<Note>
Some versions of Visual Studio may show this error in the IDE but compile the project successfully when using the package-provided compiler.
</Note>
</Accordion>
</AccordionGroup>









2 changes: 2 additions & 0 deletions fern/products/sdks/sdks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ navigation:
- page: Publishing to NuGet
path: ./overview/csharp/publishing-to-nuget.mdx
slug: publishing
- page: Version compatibility
path: ./overview/csharp/version-compatibility.mdx
- page: Adding custom code
hidden: true
path: ./overview/csharp/custom-code.mdx
Expand Down