Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
5 changes: 4 additions & 1 deletion docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@
"csharp-14.0/*.md",
"closed-hierarchies.md",
"collection-expression-arguments.md",
"unions.md"
"unions.md",
"unsafe-evolution.md"
],
"src": "_csharplang/proposals",
"dest": "csharp/language-reference/proposals",
Expand Down Expand Up @@ -647,6 +648,7 @@
"_csharplang/proposals/collection-expression-arguments.md": "Collection expression arguments",
"_csharplang/proposals/unions.md": "Unions",
"_csharplang/proposals/closed-hierarchies.md": "Closed hierarchies",
"_csharplang/proposals/unsafe-evolution.md": "Memory safety",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "C# compiler breaking changes since C# 10",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md": "C# compiler breaking changes since C# 11",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md": "C# compiler breaking changes since C# 12",
Expand Down Expand Up @@ -731,6 +733,7 @@
"_csharplang/proposals/collection-expression-arguments.md": "This proposal introduces collection expression arguments.",
"_csharplang/proposals/unions.md": "This proposal describes union types and union declarations. Unions allow expressing values from a closed set of types with exhaustive pattern matching.",
"_csharplang/proposals/closed-hierarchies.md": "This proposal describes closed class hierarchies. A closed class restricts derivation to its declaring assembly, enabling exhaustive `switch` expressions over its direct descendants.",
"_csharplang/proposals/unsafe-evolution.md": "This proposal describes the evolution of memory safety in C#. It ties the `unsafe` context to operations that access unmanaged memory, rather than to the existence of pointer types.",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "Learn about any breaking changes since the initial release of C# 10 and included in C# 11",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md": "Learn about any breaking changes since the initial release of C# 11 and included in C# 12",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md": "Learn about any breaking changes since the initial release of C# 12 and included in C# 13",
Expand Down
5 changes: 4 additions & 1 deletion docs/csharp/language-reference/keywords/unsafe.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
description: "unsafe keyword - C# Reference"
title: "unsafe keyword"
ms.date: 01/22/2026
ms.date: 06/16/2026
f1_keywords:
- "unsafe_CSharpKeyword"
- "unsafe"
Expand Down Expand Up @@ -40,6 +40,9 @@ unsafe

To compile unsafe code, you must specify the [**AllowUnsafeBlocks**](../compiler-options/language.md#allowunsafeblocks) compiler option. The common language runtime can't verify unsafe code.

> [!NOTE]
> Beginning with C# 15, the [memory safety](../unsafe-code.md#memory-safety-preview) preview feature narrows the operations that require an `unsafe` context. Creating a pointer, the `fixed` statement, and converting a `stackalloc` to a pointer no longer require an `unsafe` context. Only operations that access the pointed-to memory, such as pointer indirection, do. A later preview also changes `unsafe` on a member to mark it as *requires-unsafe*, so callers must use the member from an `unsafe` context.
Comment thread
BillWagner marked this conversation as resolved.
Outdated

## Example

:::code language="csharp" source="./snippets/csrefKeywordsModifiers.cs" id="22":::
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Pointer related operators - access memory and dereference memory locations"
description: "Learn about C# operators that you can use when working with pointers. You use these operators to access memory, index memory locations and dereference the storage at a memory location"
ms.date: 01/20/2026
ms.date: 06/16/2026
author: pkulikov
f1_keywords:
- "->_CSharpKeyword"
Expand Down Expand Up @@ -40,6 +40,9 @@ For information about pointer types, see [Pointer types](../unsafe-code.md#point
> [!NOTE]
> Any operation with pointers requires an [unsafe](../keywords/unsafe.md) context. You must compile the code that contains unsafe blocks with the [**AllowUnsafeBlocks**](../compiler-options/language.md#allowunsafeblocks) compiler option.

> [!NOTE]
> Beginning with C# 15, the [memory safety](../unsafe-code.md#memory-safety-preview) preview feature lets you use the address-of `&` operator outside an `unsafe` context. The pointer indirection, member access, and element access operators that read or write the pointed-to memory still require an `unsafe` context.
Comment thread
BillWagner marked this conversation as resolved.
Outdated

## <a name="address-of-operator-"></a> Address-of operator &amp;

The unary `&` operator returns the address of its operand:
Expand Down
5 changes: 4 additions & 1 deletion docs/csharp/language-reference/operators/sizeof.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "sizeof operator - determine the storage needs for a type"
description: "Learn about the C# `sizeof` operator that returns the memory amount occupied by a variable of a given type."
ms.date: 02/06/2026
ms.date: 06/16/2026
f1_keywords:
- "sizeof_CSharpKeyword"
- "sizeof"
Expand Down Expand Up @@ -36,6 +36,9 @@ The size of the types in the preceding table is a compile-time constant.

In [unsafe](../keywords/unsafe.md) code, you can use `sizeof` on any non-`void` type, including types constructed from type parameters.

> [!NOTE]
> Beginning with C# 15, the [memory safety](../unsafe-code.md#memory-safety-preview) preview feature lets you use `sizeof` on any unmanaged type outside an `unsafe` context.

- The size of a reference or pointer type is the size of a reference or pointer, not the size of the object it might refer to.
- The size of a value type, unmanaged or not, is the size of such a value.
- The size of a `ref struct` type is the size of the value. The size of every `ref` field is the size of a reference or pointer, not the size of the value it refers to.
Expand Down
5 changes: 4 additions & 1 deletion docs/csharp/language-reference/operators/stackalloc.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "stackalloc expression - Allocate variable storage on the stack instead of the heap"
description: "The C# stackalloc expression allocates a block of memory on the stack. Stackalloc memory is automatically discarded when that method returns."
ms.date: 01/20/2026
ms.date: 06/16/2026
f1_keywords:
- "stackalloc_CSharpKeyword"
helpviewer_keywords:
Expand Down Expand Up @@ -38,6 +38,9 @@ You can assign the result of a `stackalloc` expression to a variable of one of t

As the preceding example shows, you must use an `unsafe` context when you work with pointer types.
Comment thread
BillWagner marked this conversation as resolved.
Outdated

> [!NOTE]
> Beginning with C# 15, the [memory safety](../unsafe-code.md#memory-safety-preview) preview feature lets you convert a `stackalloc` expression to a pointer outside an `unsafe` context. Operations that access the allocated memory through the pointer still require an `unsafe` context.

For pointer types, you can use a `stackalloc` expression only in a local variable declaration to initialize the variable.

The amount of memory available on the stack is limited. If you allocate too much memory on the stack, a <xref:System.StackOverflowException> is thrown. To avoid that exception, follow these rules:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Entry point so the snippet project produces an executable and is verified by a build.
MemorySafety.Relaxations.CreatePointer();
MemorySafety.Relaxations.PinArray([1, 2, 3]);
MemorySafety.Relaxations.AllocateOnStack();
System.Console.WriteLine(MemorySafety.Relaxations.SizeOfStruct());
System.Console.WriteLine(MemorySafety.Relaxations.ReadValue([10, 20, 30]));
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// This file demonstrates the C# 15 memory safety relaxations verified against
// .NET 11 Preview 5. Creating pointers, the fixed statement, stackalloc-to-pointer,
// and sizeof no longer require an unsafe context. Only operations that access the
// pointed-to memory still require one.

namespace MemorySafety;

public class Relaxations
{
// <CreatePointer>
public static void CreatePointer()
{
int value = 42;
// Creating a pointer doesn't require an unsafe context.
int* pointer = &value;
int** pointerToPointer = &pointer;
}
// </CreatePointer>

// <FixedStatement>
public static void PinArray(int[] numbers)
{
// The fixed statement no longer requires an unsafe context.
fixed (int* first = numbers)
{
int* current = first;
}
}
// </FixedStatement>

// <StackallocToPointer>
public static void AllocateOnStack()
{
// Converting a stackalloc to a pointer no longer requires an unsafe context.
int* buffer = stackalloc int[10];
}
// </StackallocToPointer>

// <SizeOf>
public static int SizeOfStruct()
{
// sizeof of any unmanaged type no longer requires an unsafe context.
return sizeof(System.Guid);
}
// </SizeOf>

// <Dereference>
public static int ReadValue(int[] numbers)
{
fixed (int* first = numbers)
{
// Dereferencing a pointer accesses unmanaged memory, so it still
// requires an unsafe context.
unsafe
{
return *first;
}
}
}
// </Dereference>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net11.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

</Project>
5 changes: 4 additions & 1 deletion docs/csharp/language-reference/statements/fixed.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "fixed statement - pin a moveable variable"
description: "Use the C# `fixed` statement to pin a moveable variable and declare a pointer to that variable. The address of a pinned variable doesn't change during execution of the statement."
ms.date: 01/16/2026
ms.date: 06/16/2026
f1_keywords:
- "fixed_CSharpKeyword"
- "fixed"
Expand All @@ -20,6 +20,9 @@ The `fixed` statement prevents the [garbage collector](../../../standard/garbage
> [!NOTE]
> You can use the `fixed` statement only in an [unsafe](../keywords/unsafe.md) context. The code that contains unsafe blocks must be compiled by using the [**AllowUnsafeBlocks**](../compiler-options/language.md#allowunsafeblocks) compiler option.

> [!NOTE]
> Beginning with C# 15, the [memory safety](../unsafe-code.md#memory-safety-preview) preview feature lets you use the `fixed` statement outside an `unsafe` context. Only operations that access the pinned memory through the pointer, such as pointer indirection, still require an `unsafe` context.

Comment thread
BillWagner marked this conversation as resolved.
Outdated
You can initialize the declared pointer as follows:

- With an array, as the example at the beginning of this article shows. The initialized pointer contains the address of the first array element.
Expand Down
40 changes: 39 additions & 1 deletion docs/csharp/language-reference/unsafe-code.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Unsafe code, pointers to data, and function pointers"
description: Learn about unsafe code, pointers, and function pointers. C# requires you to declare an unsafe context to use these features to directly manipulate memory or function pointers (unmanaged delegates).
Comment thread
BillWagner marked this conversation as resolved.
Outdated
ms.date: 01/16/2026
ms.date: 06/16/2026
f1_keywords:
- "functionPointer_CSharpKeyword"
helpviewer_keywords:
Expand Down Expand Up @@ -31,6 +31,44 @@ Unsafe code has the following properties:

For information about best practices for unsafe code in C#, see [Unsafe code best practices](../../standard/unsafe-code/best-practices.md).

## Memory safety (preview)

> [!IMPORTANT]
> Memory safety is a preview feature in C# 15. The behavior described in this section can change before the feature ships. To try the relaxations, set the [`LangVersion`](compiler-options/language.md#langversion) compiler option to `preview`.

The memory safety feature narrows the definition of an unsafe context. Historically, the `unsafe` context covered the existence of pointer types. The updated rules tie the `unsafe` context to the operations that access memory the runtime doesn't manage. The existence of a pointer isn't unsafe; the dereference of a pointer is.

Under the updated rules, the following operations no longer require an `unsafe` context:

- Declaring a pointer type and taking the address of a variable with the `&` operator.
- The [`fixed`](statements/fixed.md) statement that pins a variable.
- Converting a [`stackalloc`](operators/stackalloc.md) expression to a pointer.
- The [`sizeof`](operators/sizeof.md) operator applied to any unmanaged type.

The following example creates and pins pointers without an `unsafe` context:

:::code language="csharp" source="snippets/memory-safety/Relaxations.cs" id="CreatePointer":::

:::code language="csharp" source="snippets/memory-safety/Relaxations.cs" id="FixedStatement":::

These relaxations apply whenever you compile with the `preview` language version, whether or not an assembly opts in to the updated memory safety rules.

The operations that access the pointed-to memory still require an `unsafe` context:

- Pointer indirection (`*p`), pointer member access (`p->member`), and pointer element access (`p[i]`).
- Function pointer invocation.
- Element access on a fixed-size buffer.

The following example pins an array without an `unsafe` context but dereferences the pointer inside one:

:::code language="csharp" source="snippets/memory-safety/Relaxations.cs" id="Dereference":::

### Updated memory safety rules

Later previews extend the meaning of `unsafe` as a member modifier. Today, `unsafe` on a member only allows pointers in the signature and body. Under the updated memory safety rules, `unsafe` on a member marks it as *requires-unsafe*: callers must use the member from an `unsafe` context. The audit obligation moves to the caller. An assembly opts in to this enforcement, and the compiler records the choice with the `System.Runtime.CompilerServices.MemorySafetyRulesAttribute` attribute. The feature also adds a `safe` contextual keyword that marks `extern` members and explicit-layout fields as safe.

The compiler in .NET 11 Preview 5 implements the pointer relaxations but doesn't yet enforce the *requires-unsafe* rules, the assembly opt-in, or the `safe` keyword. For the full design, see the [memory safety feature specification](~/_csharplang/proposals/unsafe-evolution.md).

## Pointer types

In an unsafe context, a type can be a pointer type, in addition to a value type or a reference type. A pointer type declaration takes one of the following forms:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ f1_keywords:
- "CS9357"
- "CS9358"
- "CS9359"
# unsafe evolution
# memory safety
- "CS9360"
- "CS9361"
- "CS9362"
Expand Down
4 changes: 4 additions & 0 deletions docs/csharp/specification/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ items:
items:
- name: Experimental attribute
href: ../../../_csharplang/proposals/csharp-12.0/experimental-attribute.md
- name: Unsafe code
items:
- name: Memory safety
href: ../../../_csharplang/proposals/unsafe-evolution.md
- name: Deviations from the C# standard
href: ../../../_roslyn/docs/compilers/CSharp/Deviations from Standard.md
- name: Other C# documentation
Expand Down
33 changes: 32 additions & 1 deletion docs/csharp/whats-new/csharp-15.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: What's new in C# 15
description: Get an overview of the new features in C# 15. C# 15 ships with .NET 11.
ms.date: 06/05/2026
ms.date: 06/16/2026
ms.topic: whats-new
ms.update-cycle: 365-days
ai-usage: ai-assisted
Expand All @@ -13,6 +13,7 @@ C# 15 includes the following new features. Try these features by using the lates
- [Collection expression arguments](#collection-expression-arguments)
- [Union types](#union-types)
- [Closed hierarchies](#closed-hierarchies)
- [Memory safety](#memory-safety)

C# 15 is the latest C# preview release. .NET 11 preview versions support C# 15. For more information, see [C# language versioning](../language-reference/configure-language-version.md).

Expand Down Expand Up @@ -107,6 +108,36 @@ The `closed` modifier is a contextual keyword. A `closed` class is implicitly `a

For more information, see the [closed modifier](../language-reference/keywords/closed.md) and [Closed hierarchy patterns](../language-reference/operators/patterns.md#closed-hierarchy-patterns) in the language reference, or the [feature specification](~/_csharplang/proposals/closed-hierarchies.md). You can copy the examples in this section, including the `ClosedAttribute` workaround, from the [keywords snippets project](https://github.com/dotnet/docs/blob/main/docs/csharp/language-reference/keywords/snippets/shared) in the `dotnet/docs` GitHub repository.

## Memory safety

C# 15 begins to evolve the rules for unsafe code. Historically, the `unsafe` context covered the existence of pointer types. The updated rules tie the `unsafe` context to the operations that access unmanaged memory, not to the existence of a pointer.

When you compile with the `preview` language version, the following operations no longer require an `unsafe` context:

- Declaring a pointer type and taking the address of a variable with the `&` operator.
- The `fixed` statement that pins a variable.
- Converting a `stackalloc` expression to a pointer.
- The `sizeof` operator applied to any unmanaged type.

The following example creates and pins a pointer without an `unsafe` context:

```csharp
int number = 42;
int* pointer = &number;

int[] numbers = [10, 20, 30];
fixed (int* first = numbers)
{
// Dereferencing the pointer still requires an unsafe context.
}
```

The operations that access the pointed-to memory, such as pointer indirection (`*p`), pointer member access (`p->member`), pointer element access (`p[i]`), and function pointer invocation, still require an `unsafe` context.

The .NET 11 Preview 5 compiler implements these relaxations. A later preview adds the *requires-unsafe* member model, the assembly opt-in to the updated memory safety rules, and the `safe` contextual keyword.

For more information, see [Unsafe code, pointer types, and function pointers](../language-reference/unsafe-code.md#memory-safety-preview) in the language reference or the [feature specification](~/_csharplang/proposals/unsafe-evolution.md).

<!-- Add when available
## See also

Expand Down
Loading