C# and .NET

This section documents C# 14 on .NET 10 (LTS), as published at learn.microsoft.com/dotnet/csharp, which is the reference these pages are written and verified against. Features introduced by C# 15 / .NET 11 are still in preview and are always flagged as such — never presented as baseline.

This content was generated with the assistance of AI and should be verified against learn.microsoft.com before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

C# is a language; .NET is the platform it targets. The language specification defines syntax and semantics, but almost everything you actually call — Console, List<T>, Task, string — comes from .NET’s class library, and everything you run is executed by a .NET runtime. Knowing where the boundary lies explains a lot: why int and System.Int32 are the same type, why async needs no runtime support beyond Task, and why a language feature can ship only when the runtime is ready for it.

Managed Execution and the CLR

The Common Language Runtime is the execution engine. Compiling produces IL (intermediate language) plus metadata; the CLR loads that and provides:

  • JIT compilation — IL is compiled to native code method by method, on first call. Tiered compilation compiles quickly at first (tier 0) and then recompiles hot methods with full optimisation (tier 1), using profile data gathered from the running program (dynamic PGO).

  • Automatic memory management — a generational, compacting garbage collector. See Memory Management and Disposal.

  • Type safety and verification — the runtime enforces that IL respects the type system.

  • Exception handling, threading, reflection and interop services.

"Managed code" simply means code the CLR executes and whose memory it owns. unsafe blocks and P/Invoke step outside that guarantee deliberately; see Unsafe Code, Spans and Performance and Native Interop.

Native AOT is the alternative: dotnet publish -p:PublishAot=true compiles the whole application ahead of time into a self-contained native executable with no JIT and no IL at run time. Startup is near-instant and memory use drops, at the cost of dynamic capabilities — runtime code generation and unbounded reflection are not available, and the whole program must be statically analysable for trimming.

flowchart TB subgraph APP["Your application"] A1["C# / F# / VB source"] A2["NuGet packages"] end subgraph BCL["Base Class Library (System.*)"] B1["System.Collections, System.Linq"] B2["System.Threading, System.Text.Json"] B3["System.IO, System.Net"] end subgraph CLR["Common Language Runtime"] C1["JIT / tiered compilation"] C2["Garbage collector"] C3["Type loader, verification"] C4["Exceptions, threads, interop"] end subgraph OS["Operating system"] D1["Windows / Linux / macOS / Android / iOS / browser (Wasm)"] end APP --> BCL --> CLR --> OS

Assemblies and Metadata

An assembly is the unit of deployment, versioning and (with internal) accessibility: a .dll or .exe holding IL, metadata and resources. Metadata is a complete, machine-readable description of every type and member in the assembly — which is why C# needs no header files, why IntelliSense works against a compiled library, and why reflection can enumerate types it has never seen before.

using System.Reflection;

Assembly assembly = typeof(string).Assembly;

Console.WriteLine(assembly.FullName);      // System.Private.CoreLib, Version=10.0.0.0, ...
Console.WriteLine(assembly.Location);      // path to the loaded file

// Metadata is queryable at run time.
foreach (MethodInfo method in typeof(string).GetMethods(BindingFlags.Public | BindingFlags.Static)
                                            .Take(3))
{
    Console.WriteLine(method.Name);
}

See Namespaces, Assemblies and Projects for how assemblies are produced and referenced, and Attributes and Reflection for reading metadata.

The Base Class Library

The BCL is the standard library every .NET language shares. The C# language keyword int is an alias for the BCL type System.Int32 — they are interchangeable, and int.MaxValue and System.Int32.MaxValue are the same member:

int a = 42;
System.Int32 b = 42;
Console.WriteLine(a == b);           // True -- the same type
Console.WriteLine(typeof(int) == typeof(System.Int32));  // True

The same holds for string/String, bool/Boolean, object/Object and the rest. The house convention (and Microsoft’s) is to use the C# keyword when referring to the type and the framework name when calling a static member, e.g. int.Parse is written int.Parse, not Int32.Parse.

The Runtimes

Runtime What it is

.NET 10

The current cross-platform runtime — the one these pages assume. Successor to .NET Core; runs on Windows, Linux, macOS, and (through workloads) Android, iOS and WebAssembly.

.NET Framework 4.8.1

The original Windows-only runtime. Still supported as a component of Windows, but frozen — it receives no new language or library features. Maximum practical language version is C# 7.3.

Mono

The runtime used for Android, iOS, and Blazor WebAssembly, shipped as part of .NET and selected automatically by the relevant workload.

Blazor WebAssembly

.NET compiled to run inside the browser’s WebAssembly sandbox, with an optional AOT mode for hot paths.

Support Policy: LTS and STS

Releases alternate. LTS (even-numbered: .NET 8, .NET 10) are supported for three years; STS (odd-numbered: .NET 9, .NET 11) for 18 months. Both are production-quality; the difference is only the support window. .NET 10 was released in November 2025 and is supported into November 2028.

Target Framework Monikers and Language Versions

A project declares what it targets with a TFM:

<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

Platform-specific TFMs add an OS and optionally a version: net10.0-windows, net10.0-android, net10.0-ios18.0. A library can multi-target:

<PropertyGroup>
  <TargetFrameworks>net10.0;net8.0;netstandard2.0</TargetFrameworks>
</PropertyGroup>

The TFM also picks the default language version. net10.0 defaults to C# 14, net9.0 to C# 13, net8.0 to C# 12, and netstandard2.0 to C# 7.3. Setting <LangVersion> explicitly is supported but only lowers risk in one direction: features that need runtime or library support (records need IsExternalInit, required needs RequiredMemberAttribute, generic math needs static abstract interface members) will not work on an older TFM just because the compiler accepts the syntax.

<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <LangVersion>preview</LangVersion>   <!-- opt into C# 15 preview features -->
</PropertyGroup>
Preview features (C# 15 / .NET 11)

Everything in this section labelled C# 15 preview requires a .NET 11 preview SDK and <LangVersion>preview</LangVersion>. Preview features may change or be withdrawn before release and are not covered by the support policy above. The preview snippets in this section are written from the published feature specifications rather than compiled against a shipped compiler.

.NET Standard: The Legacy Bridge

netstandard2.0 is a specification of APIs, not a runtime — a way for one library to be consumed by .NET Framework, Mono and .NET Core alike. It was superseded by the unified net<version> TFMs, and new libraries should target net10.0 (adding netstandard2.0 only if .NET Framework consumers still matter). .NET Standard 2.1 was never implemented by .NET Framework, which is why 2.0 is the practical floor for that scenario.

Interoperating with F# and Visual Basic

All .NET languages compile to the same IL and share the same type system, so a C# project can reference an F# or VB project directly and call its types with no shim. A few practical notes:

  • F# records, discriminated unions and functions surface to C# as classes and FSharpFunc values; the [<CLIMutable>] and module attributes control how friendly that surface is.

  • VB’s late binding (Option Strict Off) maps to C#'s dynamic.

  • The Common Language Specification (CLS) defines the subset of the type system every language must understand. Marking a public API [assembly: CLSCompliant(true)] makes the compiler warn about members — unsigned types in public signatures, identifiers differing only in case — that other languages could not consume.

See Also