From b4b70bd9b0a92ded56ee242d821eb34da8a98c35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:09:40 -0700 Subject: [PATCH 1/6] Bump the dotnet group with 2 updates (#54460) Bumps Aspire.Azure.Data.Tables from 13.4.2 to 13.4.3 Bumps Aspire.StackExchange.Redis from 13.4.2 to 13.4.3 --- updated-dependencies: - dependency-name: Aspire.Azure.Data.Tables dependency-version: 13.4.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dotnet - dependency-name: Aspire.StackExchange.Redis dependency-version: 13.4.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dotnet - dependency-name: Aspire.StackExchange.Redis dependency-version: 13.4.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dotnet ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/orleans/host/snippets/aspire/Client/Client.csproj | 2 +- .../snippets/aspire/ServiceDefaults/ServiceDefaults.csproj | 2 +- docs/orleans/host/snippets/aspire/Silo/Silo.csproj | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/orleans/host/snippets/aspire/Client/Client.csproj b/docs/orleans/host/snippets/aspire/Client/Client.csproj index e7894449d5711..a997ba897b894 100644 --- a/docs/orleans/host/snippets/aspire/Client/Client.csproj +++ b/docs/orleans/host/snippets/aspire/Client/Client.csproj @@ -8,7 +8,7 @@ - + diff --git a/docs/orleans/host/snippets/aspire/ServiceDefaults/ServiceDefaults.csproj b/docs/orleans/host/snippets/aspire/ServiceDefaults/ServiceDefaults.csproj index 165d6794ae835..49e170ad0fbd5 100644 --- a/docs/orleans/host/snippets/aspire/ServiceDefaults/ServiceDefaults.csproj +++ b/docs/orleans/host/snippets/aspire/ServiceDefaults/ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/orleans/host/snippets/aspire/Silo/Silo.csproj b/docs/orleans/host/snippets/aspire/Silo/Silo.csproj index 07ec70e190f9c..9eaeab3613957 100644 --- a/docs/orleans/host/snippets/aspire/Silo/Silo.csproj +++ b/docs/orleans/host/snippets/aspire/Silo/Silo.csproj @@ -8,12 +8,12 @@ - + - + From 440a975c01dc39f96f8ac6020f337049fcd4c4e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:11:50 -0700 Subject: [PATCH 2/6] Bump Microsoft.Extensions.Http and Microsoft.Extensions.Logging.Abstractions (#54459) Bumps Microsoft.Extensions.Http from 10.0.8 to 10.0.9 Bumps Microsoft.Extensions.Logging.Abstractions from 10.0.8 to 10.0.9 --- updated-dependencies: - dependency-name: Microsoft.Extensions.Http dependency-version: 10.0.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dotnet - dependency-name: Microsoft.Extensions.Logging.Abstractions dependency-version: 10.0.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: dotnet ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../BroadcastChannel.Silo/BroadcastChannel.Silo.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/orleans/streaming/snippets/broadcastchannel/BroadcastChannel.Silo/BroadcastChannel.Silo.csproj b/docs/orleans/streaming/snippets/broadcastchannel/BroadcastChannel.Silo/BroadcastChannel.Silo.csproj index 03d6e778658db..15cd83349cfa1 100644 --- a/docs/orleans/streaming/snippets/broadcastchannel/BroadcastChannel.Silo/BroadcastChannel.Silo.csproj +++ b/docs/orleans/streaming/snippets/broadcastchannel/BroadcastChannel.Silo/BroadcastChannel.Silo.csproj @@ -5,7 +5,7 @@ - + From efeb65e6408719d7ff91e25601fc7d9693dca7bc Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Fri, 19 Jun 2026 09:11:07 -0700 Subject: [PATCH 3/6] Delete .github/workflows/stale.yml (#54474) --- .github/workflows/stale.yml | 35 ----------------------------------- README.md | 1 - 2 files changed, 36 deletions(-) delete mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 9f2e04aa5a499..0000000000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Close stale issues - -on: - schedule: # Run 5 minutes after midnight daily. - - cron: '5 0 * * *' - -permissions: - contents: read - -jobs: - stale: - - runs-on: ubuntu-latest - permissions: - issues: write - - steps: - - name: Harden Runner - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 - with: - egress-policy: audit - - - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 - with: - start-date: '2022-03-01T00:00:00Z' # ISO 8601 or RFC 2822 - repo-token: ${{ secrets.GITHUB_TOKEN }} - days-before-stale: 180 - days-before-close: 14 - stale-issue-label: stale - close-issue-message: > - This issue has been automatically closed due to lack of activity. - If you feel this issue is still important, please reopen it and leave a comment. - exempt-issue-labels: 'breaking-change,Pri0,:watch: Not Triaged,:checkered_flag: Release: .NET 7,:checkered_flag: Release: .NET 8' - operations-per-run: 50 - debug-only: true diff --git a/README.md b/README.md index 1f2cc1cdaf391..b5470c9b15e0c 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,6 @@ to clarify expected behavior in our community. For more information, see the [.N ## :octocat: GitHub Action workflows - [![Live branch protection](https://github.com/dotnet/docs/actions/workflows/live-protection.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/live-protection.yml): Adds a comment to PRs that were not automated, but rather manually created that target the `live` branch. -- [![Close stale issues](https://github.com/dotnet/docs/actions/workflows/stale.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/stale.yml): Closes stale issues that have not been updated in 180 days. - [![`dependabot` auto-approve and auto-merge](https://github.com/dotnet/docs/actions/workflows/dependabot-approve-and-automerge.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/dependabot-approve-and-automerge.yml): Automatically approves and auto-merges PRs originating from the `dependabbot[bot]`. - [![Generate what's new article](https://github.com/dotnet/docs/actions/workflows/whats-new.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/whats-new.yml): Creates a PR to generate the "What's new" article on the first of every month. - [![Markdownlint](https://github.com/dotnet/docs/actions/workflows/markdownlint.yml/badge.svg)](https://github.com/dotnet/docs/actions/workflows/markdownlint.yml): The current status for the entire repositories Markdown linter status. From 140575f1dbbdcb3e54d52647ec36e7e4315ef449 Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Mon, 22 Jun 2026 14:58:59 +0100 Subject: [PATCH 4/6] [Metrics] Update Prometheus snippets (#54486) Update snippets using the OpenTelemetry Prometheus exporters to the latest version and remove obsolete `UriPrefixes` property usage. Fixes https://github.com/open-telemetry/opentelemetry-dotnet/issues/7268. --- .gitignore | 1 + docs/core/diagnostics/snippets/Metrics/Program.cs | 2 +- .../diagnostics/snippets/Metrics/metric-instr.csproj | 2 +- .../csharp/OTel-Prometheus-Grafana-Jaeger.csproj | 10 +++++----- .../snippets/metrics/HelloBuiltinMetrics.csproj | 4 ++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index aebb12d48e08f..87aaab5541d5d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ docs/_build/ .idea/ +*.tmp *.swp rebuild.cmd diff --git a/docs/core/diagnostics/snippets/Metrics/Program.cs b/docs/core/diagnostics/snippets/Metrics/Program.cs index fd985d23e1b73..b6f444546c34e 100644 --- a/docs/core/diagnostics/snippets/Metrics/Program.cs +++ b/docs/core/diagnostics/snippets/Metrics/Program.cs @@ -40,7 +40,7 @@ static void Main(string[] args) { using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter("HatCo.HatStore") - .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { "http://localhost:9184/" }) + .AddPrometheusHttpListener(options => options.Port = 9184) .Build(); var rand = Random.Shared; diff --git a/docs/core/diagnostics/snippets/Metrics/metric-instr.csproj b/docs/core/diagnostics/snippets/Metrics/metric-instr.csproj index b837886fdb025..c7ba5f6123157 100644 --- a/docs/core/diagnostics/snippets/Metrics/metric-instr.csproj +++ b/docs/core/diagnostics/snippets/Metrics/metric-instr.csproj @@ -8,7 +8,7 @@ - + diff --git a/docs/core/diagnostics/snippets/OTel-Prometheus-Grafana-Jaeger/csharp/OTel-Prometheus-Grafana-Jaeger.csproj b/docs/core/diagnostics/snippets/OTel-Prometheus-Grafana-Jaeger/csharp/OTel-Prometheus-Grafana-Jaeger.csproj index f9ad3285f642e..19d64a77d7d83 100644 --- a/docs/core/diagnostics/snippets/OTel-Prometheus-Grafana-Jaeger/csharp/OTel-Prometheus-Grafana-Jaeger.csproj +++ b/docs/core/diagnostics/snippets/OTel-Prometheus-Grafana-Jaeger/csharp/OTel-Prometheus-Grafana-Jaeger.csproj @@ -13,11 +13,11 @@ - - - - - + + + + + diff --git a/docs/fundamentals/networking/telemetry/snippets/metrics/HelloBuiltinMetrics.csproj b/docs/fundamentals/networking/telemetry/snippets/metrics/HelloBuiltinMetrics.csproj index e9e6d6a9f5511..d6fc7c92fa4e4 100644 --- a/docs/fundamentals/networking/telemetry/snippets/metrics/HelloBuiltinMetrics.csproj +++ b/docs/fundamentals/networking/telemetry/snippets/metrics/HelloBuiltinMetrics.csproj @@ -8,8 +8,8 @@ - - + + From f33a247712bb85d2948f2bd59021e55159e9da71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Mon, 22 Jun 2026 16:03:07 +0200 Subject: [PATCH 5/6] Document MSTEST0070 (MemberCondition arguments should be valid) (#54478) * Document MSTEST0070 (MemberCondition arguments should be valid) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review: use 'runtime', backtick attribute names in tables, add MSTEST0070 to Test configuration list Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../testing/mstest-analyzers/mstest0070.md | 146 ++++++++++++++++++ .../core/testing/mstest-analyzers/overview.md | 2 + .../testing/mstest-analyzers/usage-rules.md | 2 + docs/navigate/devops-testing/toc.yml | 2 + 4 files changed, 152 insertions(+) create mode 100644 docs/core/testing/mstest-analyzers/mstest0070.md diff --git a/docs/core/testing/mstest-analyzers/mstest0070.md b/docs/core/testing/mstest-analyzers/mstest0070.md new file mode 100644 index 0000000000000..cbe6df66d3be7 --- /dev/null +++ b/docs/core/testing/mstest-analyzers/mstest0070.md @@ -0,0 +1,146 @@ +--- +title: "MSTEST0070: [MemberCondition] arguments should be valid" +description: "Learn about code analysis rule MSTEST0070: [MemberCondition] arguments should be valid" +ms.date: 06/19/2026 +f1_keywords: +- MSTEST0070 +- MemberConditionShouldBeValidAnalyzer +helpviewer_keywords: +- MemberConditionShouldBeValidAnalyzer +- MSTEST0070 +author: evangelink +ms.author: amauryleve +ai-usage: ai-assisted +dev_langs: +- CSharp +--- +# MSTEST0070: \[MemberCondition] arguments should be valid + +| Property | Value | +|-------------------------------------|----------------------------------------------------| +| **Rule ID** | MSTEST0070 | +| **Title** | \[MemberCondition] arguments should be valid | +| **Category** | Usage | +| **Fix is breaking or non-breaking** | Non-breaking | +| **Enabled by default** | Yes | +| **Default severity** | Warning | +| **Introduced in version** | 4.3.0 | +| **Is there a code fix** | No | + +## Cause + +A `[MemberCondition]` attribute references a member that the test framework can't evaluate. The referenced member must exist on the referenced type, be `public static`, return `bool`, and (for methods) be parameterless. + +## Rule description + +The `[MemberCondition]` attribute conditionally runs or skips a test class or test method based on the value of one or more static `bool` members. You reference each member by its declaring type and name, for example `[MemberCondition(typeof(MyConditions), nameof(MyConditions.IsEnabled))]`. + +Because the member is referenced by name, the test framework resolves it through reflection at runtime. If the member doesn't exist, isn't `public`, isn't `static`, doesn't return `bool`, requires parameters, or is a write-only property, evaluating the condition throws an and the test fails. This analyzer surfaces those problems at build time so that typos and refactors don't silently break test gating. + +The rule reports a violation when a referenced member: + +- Can't be found on the referenced type. +- Isn't `public`. +- Isn't `static`. +- Isn't a property, field, or parameterless method. +- Doesn't return `bool`. +- Is a method that declares parameters. +- Is a property without a getter. + +```csharp +public class TestConditions +{ + public static bool IsEnabled => true; + private static bool IsHidden => true; // not public + public bool IsInstance => true; // not static + public static int Count => 0; // wrong return type + public static bool IsReady(string mode) => true; // has parameters +} + +[TestClass] +public class MyTestClass +{ + [TestMethod] + [MemberCondition(typeof(TestConditions), "IsEnable")] // Violation - member not found (typo) + public void Test1() + { + } + + [TestMethod] + [MemberCondition(typeof(TestConditions), nameof(TestConditions.IsInstance))] // Violation - not static + public void Test2() + { + } + + [TestMethod] + [MemberCondition(typeof(TestConditions), nameof(TestConditions.Count))] // Violation - doesn't return bool + public void Test3() + { + } +} +``` + +## How to fix violations + +Reference a member that is `public static`, returns `bool`, and (for methods) is parameterless. Fix the member name if it's a typo, or adjust the member declaration so it meets the requirements. + +```csharp +public class TestConditions +{ + public static bool IsEnabled => true; +} + +[TestClass] +public class MyTestClass +{ + [TestMethod] + [MemberCondition(typeof(TestConditions), nameof(TestConditions.IsEnabled))] + public void Test1() + { + } +} +``` + +Use `nameof` instead of a string literal so the compiler and this rule keep the reference in sync when you rename the member. + +You can also reference a field or a parameterless method, combine several members (they're AND-combined), and use to invert the condition: + +```csharp +[TestMethod] +[MemberCondition( + typeof(TestConditions), + nameof(TestConditions.IsEnabled), + nameof(TestConditions.IsSupported))] // runs only when both are true +public void Test2() +{ +} + +[TestMethod] +[MemberCondition(ConditionMode.Exclude, typeof(TestConditions), nameof(TestConditions.IsLegacy))] // skipped when true +public void Test3() +{ +} +``` + +## When to suppress warnings + +Don't suppress warnings from this rule. A `[MemberCondition]` attribute that references an invalid member throws at runtime and causes the test to fail rather than evaluate the condition. + +## Suppress a warning + +If you just want to suppress a single violation, add preprocessor directives to your source file to disable and then re-enable the rule. + +```csharp +#pragma warning disable MSTEST0070 +// The code that's violating the rule is on this line. +#pragma warning restore MSTEST0070 +``` + +To disable the rule for a file, folder, or project, set its severity to `none` in the [configuration file](../../../fundamentals/code-analysis/configuration-files.md). + +```ini +[*.{cs,vb}] +dotnet_diagnostic.MSTEST0070.severity = none +``` + +For more information, see [How to suppress code analysis warnings](../../../fundamentals/code-analysis/suppress-warnings.md). diff --git a/docs/core/testing/mstest-analyzers/overview.md b/docs/core/testing/mstest-analyzers/overview.md index 97d5e82dbdbe6..ac5303f6e7664 100644 --- a/docs/core/testing/mstest-analyzers/overview.md +++ b/docs/core/testing/mstest-analyzers/overview.md @@ -209,6 +209,7 @@ Rules for configuring test execution, parallelization, and other test settings: - [MSTEST0055](mstest0055.md) - Do not ignore string method return value - [MSTEST0059](mstest0059.md) - Use Parallelize attribute correctly - [MSTEST0061](mstest0061.md) - Use OSCondition attribute instead of runtime check +- [MSTEST0070](mstest0070.md) - `[MemberCondition]` arguments should be valid Related documentation: [Configure MSTest](../unit-testing-mstest-configure.md), [Running tests](../unit-testing-mstest-running-tests.md) @@ -278,6 +279,7 @@ Related documentation: [Configure MSTest](../unit-testing-mstest-configure.md), | [MSTEST0061](mstest0061.md) | Usage | Use OSCondition attribute instead of runtime check | Info | | [MSTEST0062](mstest0062.md) | Usage | Avoid out/ref test method parameters | Warning | | [MSTEST0063](mstest0063.md) | Usage | Test class should have valid constructor | Warning | +| [MSTEST0070](mstest0070.md) | Usage | `[MemberCondition]` arguments should be valid | Warning | \* Escalated to Error in `Recommended` and `All` modes. diff --git a/docs/core/testing/mstest-analyzers/usage-rules.md b/docs/core/testing/mstest-analyzers/usage-rules.md index d81af85ce39dd..bc3a48d297726 100644 --- a/docs/core/testing/mstest-analyzers/usage-rules.md +++ b/docs/core/testing/mstest-analyzers/usage-rules.md @@ -59,6 +59,7 @@ Usage rules support proper usage of MSTest attributes, methods, and patterns. Th | [MSTEST0061](mstest0061.md) | Use OSCondition attribute instead of runtime check. | Info | Yes | | [MSTEST0062](mstest0062.md) | Avoid out/ref test method parameters. | Warning | Yes | | [MSTEST0063](mstest0063.md) | Test class should have valid constructor. | Warning | No | +| [MSTEST0070](mstest0070.md) | `[MemberCondition]` arguments should be valid. | Warning | No | \* Escalated to Error in `Recommended` and `All` modes. @@ -141,6 +142,7 @@ Rules for asynchronous test code: - **[MSTEST0059](mstest0059.md)**: Don't use both Parallelize and DoNotParallelize. - **[MSTEST0060](mstest0060.md)**: Avoid duplicate TestMethodAttribute. - **[MSTEST0061](mstest0061.md)**: Use OSCondition attribute for platform checks. +- **[MSTEST0070](mstest0070.md)**: `[MemberCondition]` arguments must reference valid members. ## Related documentation diff --git a/docs/navigate/devops-testing/toc.yml b/docs/navigate/devops-testing/toc.yml index 1f4b9de4bb2f0..a37a24b53360b 100644 --- a/docs/navigate/devops-testing/toc.yml +++ b/docs/navigate/devops-testing/toc.yml @@ -222,6 +222,8 @@ items: href: ../../core/testing/mstest-analyzers/mstest0062.md - name: MSTEST0063 href: ../../core/testing/mstest-analyzers/mstest0063.md + - name: MSTEST0070 + href: ../../core/testing/mstest-analyzers/mstest0070.md - name: Migration items: - name: Migrate from MSTest v1 to v3 From 1eb0214c11c2e2142463d70ac43efa831262caf3 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 22 Jun 2026 12:49:15 -0400 Subject: [PATCH 6/6] Add language reference material for `unsafe` updates for memory safety (#54367) * First pass * Use the preferred terms Use the preferred terms for memory safety. * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Major content edits * Add safe keyword and XML docs Include the tag in the recommended tags, and add the `safe` contextual keyword. * final proofread * Fix warnings. * Apply suggestions from code review Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> --- docfx.json | 5 +- .../language-reference/keywords/index.md | 1 + .../language-reference/keywords/safe.md | 64 ++++ .../language-reference/keywords/unsafe.md | 8 +- .../operators/pointer-related-operators.md | 7 +- .../language-reference/operators/sizeof.md | 9 +- .../operators/stackalloc.md | 19 +- .../snippets/memory-safety/Program.cs | 6 + .../snippets/memory-safety/Relaxations.cs | 61 ++++ .../memory-safety/memory-safety.csproj | 12 + .../language-reference/statements/fixed.md | 7 +- docs/csharp/language-reference/toc.yml | 2 + docs/csharp/language-reference/unsafe-code.md | 293 ++++++++++++++++-- .../xmldoc/recommended-tags.md | 28 +- ...n-t-have-specifics-on-this-csharp-error.md | 2 +- docs/csharp/specification/toc.yml | 4 + docs/csharp/whats-new/csharp-15.md | 37 ++- 17 files changed, 503 insertions(+), 62 deletions(-) create mode 100644 docs/csharp/language-reference/keywords/safe.md create mode 100644 docs/csharp/language-reference/snippets/memory-safety/Program.cs create mode 100644 docs/csharp/language-reference/snippets/memory-safety/Relaxations.cs create mode 100644 docs/csharp/language-reference/snippets/memory-safety/memory-safety.csproj diff --git a/docfx.json b/docfx.json index cd329615fd41a..1104350157a85 100644 --- a/docfx.json +++ b/docfx.json @@ -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", @@ -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", @@ -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", diff --git a/docs/csharp/language-reference/keywords/index.md b/docs/csharp/language-reference/keywords/index.md index 3a1af16f84f03..6f9a5e4899cae 100644 --- a/docs/csharp/language-reference/keywords/index.md +++ b/docs/csharp/language-reference/keywords/index.md @@ -155,6 +155,7 @@ A contextual keyword provides a specific meaning in the code, but it isn't a res [`record`](../../fundamentals/types/records.md) [`remove`](remove.md) [`required`](required.md) + [`safe`](safe.md) :::column-end::: :::column::: [`scoped`](../statements/declarations.md#scoped-ref) diff --git a/docs/csharp/language-reference/keywords/safe.md b/docs/csharp/language-reference/keywords/safe.md new file mode 100644 index 0000000000000..1b125e6e90b5d --- /dev/null +++ b/docs/csharp/language-reference/keywords/safe.md @@ -0,0 +1,64 @@ +--- +description: "safe modifier - C# Reference" +title: "safe modifier" +ms.date: 06/17/2026 +ai-usage: ai-assisted +f1_keywords: + - "safe_CSharpKeyword" + - "safe" +helpviewer_keywords: + - "safe keyword [C#]" +--- +# safe (C# Reference) + +The `safe` contextual keyword attests that a declaration is sound in places where the [updated memory safety model](../unsafe-code.md#the-updated-memory-safety-model-preview) requires you to make the safety choice explicit. You apply `safe` as a modifier on a declaration that the compiler can't classify on its own, such as an `extern` member or a field in a struct with explicit layout. The `safe` modifier is the counterpart to [`unsafe`](unsafe.md): `safe` attests that callers need no `unsafe` context, while `unsafe` propagates the obligation to audit safety to the caller. + +> [!IMPORTANT] +> The `safe` keyword is part of the updated memory safety model, a preview feature in C# 15 and .NET 11. The compiler in .NET 11 Preview 5 doesn't yet recognize the keyword. To follow the feature, set the [`LangVersion`](../compiler-options/language.md#langversion) compiler option to `preview`. For the full design, see the [memory safety feature specification](~/_csharplang/proposals/unsafe-evolution.md). The code in this article shows the proposed syntax and doesn't compile with the current preview compiler. + +## Extern members + +An `extern` member calls into native code, so the compiler can't classify its safety. Under the updated model, you mark every `extern` declaration, including a `LibraryImport` partial method, either `safe` or `unsafe`: + +```csharp +// Preview: illustrates the updated model, which the current compiler doesn't enforce yet. +[LibraryImport("libc")] +internal static safe partial int getpid(); + +[LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)] +internal static unsafe partial nint strlen(byte* str); +``` + +`getpid` takes no parameters and returns a primitive, so the author attests that the call is safe, and callers use it without an `unsafe` context. `strlen` takes a raw pointer that the native code dereferences, so the declaration is `unsafe` and propagates the obligation to its callers. Omitting both modifiers is an error, which forces you to make the safety decision. + +## Explicit-layout fields + +In a struct with `[StructLayout(LayoutKind.Explicit)]`, fields can overlap in memory, so the compiler can't reason about whether a read through one field is sound. You mark every field of such a struct either `safe` or `unsafe`: + +```csharp +// Preview +[StructLayout(LayoutKind.Explicit)] +internal struct Union +{ + [FieldOffset(0)] + internal safe int AsInt; + + [FieldOffset(0)] + internal safe float AsFloat; +} +``` + +A field that holds a native pointer, or whose type otherwise carries an invariant the type system can't express, is `unsafe`. A field whose type is fully described by the type system is `safe`. As with `extern` members, omitting both modifiers is an error. + +## C# language specification + +For more information, see [Unsafe code](~/_csharpstandard/standard/unsafe-code.md) in the [C# Language Specification](~/_csharpstandard/standard/README.md). The language specification is the definitive source for C# syntax and usage. + +For the design of the updated memory safety model, see the [memory safety feature specification](~/_csharplang/proposals/unsafe-evolution.md). + +## See also + +- [C# keywords](index.md) +- [`unsafe` keyword](unsafe.md) +- [`extern` modifier](extern.md) +- [Unsafe code, pointer types, and function pointers](../unsafe-code.md) diff --git a/docs/csharp/language-reference/keywords/unsafe.md b/docs/csharp/language-reference/keywords/unsafe.md index 36f60fff129ab..8783ac231b49b 100644 --- a/docs/csharp/language-reference/keywords/unsafe.md +++ b/docs/csharp/language-reference/keywords/unsafe.md @@ -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" @@ -40,6 +40,11 @@ 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] +> The [memory safety](../unsafe-code.md#the-updated-memory-safety-model-preview) preview feature available in C# 15 narrows the operations that require an `unsafe` context. +> An `unsafe` context is no longer required for creating a pointer, the `fixed` statement, converting a `stackalloc` expression to a pointer, and using `sizeof` on an unmanaged type. +> Only operations that access the pointed-to memory, such as pointer indirection, still require an `unsafe` context. 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. + ## Example :::code language="csharp" source="./snippets/csrefKeywordsModifiers.cs" id="22"::: @@ -51,6 +56,7 @@ For more information, see [Unsafe code](~/_csharpstandard/standard/unsafe-code.m ## See also - [C# keywords](index.md) +- [`safe` modifier](safe.md) - [`fixed` statement](../statements/fixed.md) - [Unsafe code, pointer types, and function pointers](../unsafe-code.md) - [Pointer related operators](../operators/pointer-related-operators.md) diff --git a/docs/csharp/language-reference/operators/pointer-related-operators.md b/docs/csharp/language-reference/operators/pointer-related-operators.md index 59643af0d14a3..9b18b75696209 100644 --- a/docs/csharp/language-reference/operators/pointer-related-operators.md +++ b/docs/csharp/language-reference/operators/pointer-related-operators.md @@ -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" @@ -38,7 +38,8 @@ Use the following operators to work with pointers: For information about pointer types, see [Pointer types](../unsafe-code.md#pointer-types). > [!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. +> Most operations with pointers require an [unsafe](../keywords/unsafe.md) context, and you must compile unsafe code with the [**AllowUnsafeBlocks**](../compiler-options/language.md#allowunsafeblocks) compiler option. +> The [memory safety](../unsafe-code.md#the-updated-memory-safety-model-preview) preview feature available in C# 15 lets you use the address-of `&` operator outside an `unsafe` context. Even when enabled, the pointer indirection, member access, and element access operators that read or write the pointed-to memory still require an `unsafe` context. ## Address-of operator & @@ -166,7 +167,7 @@ Use parentheses, `()`, to change the order of evaluation imposed by operator pre For the complete list of C# operators ordered by precedence level, see the [Operator precedence](index.md#operator-precedence) section of the [C# operators](index.md) article. -## Operator overloadability +## Operator overloading You can't overload the pointer-related operators `&`, `*`, `->`, and `[]` in a user-defined type. diff --git a/docs/csharp/language-reference/operators/sizeof.md b/docs/csharp/language-reference/operators/sizeof.md index 92df7d08e2a48..159618d9bb89e 100644 --- a/docs/csharp/language-reference/operators/sizeof.md +++ b/docs/csharp/language-reference/operators/sizeof.md @@ -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" @@ -10,7 +10,7 @@ helpviewer_keywords: --- # sizeof operator - determine the memory needs for a given type -The `sizeof` operator returns the number of bytes occupied by a variable of a given type. In safe code, the argument to the `sizeof` operator must be the name of a built-in [unmanaged type](../builtin-types/unmanaged-types.md) whose size is not platform-dependent. +The `sizeof` operator returns the number of bytes occupied by a variable of a given type. In safe code, the argument to the `sizeof` operator must be the name of a built-in [unmanaged type](../builtin-types/unmanaged-types.md) whose size isn't platform-dependent. [!INCLUDE[csharp-version-note](../includes/initial-version.md)] @@ -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] +> The [memory safety](../unsafe-code.md#the-updated-memory-safety-model-preview) preview feature available in C# 15 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. @@ -52,7 +55,7 @@ The `sizeof` operator returns the number of bytes allocated by the common langua ## C# language specification -For more information, see [The sizeof operator](~/_csharpstandard/standard/unsafe-code.md#2469-the-sizeof-operator) section of the [C# language specification](~/_csharpstandard/standard/README.md). +For more information, see the [`sizeof` operator](~/_csharpstandard/standard/unsafe-code.md#2469-the-sizeof-operator) section of the [C# language specification](~/_csharpstandard/standard/README.md). ## See also diff --git a/docs/csharp/language-reference/operators/stackalloc.md b/docs/csharp/language-reference/operators/stackalloc.md index c0ef68519550e..3180b3d3a6650 100644 --- a/docs/csharp/language-reference/operators/stackalloc.md +++ b/docs/csharp/language-reference/operators/stackalloc.md @@ -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: @@ -9,7 +9,7 @@ helpviewer_keywords: --- # stackalloc expression (C# reference) -A `stackalloc` expression allocates a block of memory on the stack. A stack-allocated memory block created during the method execution is automatically discarded when that method returns. You can't explicitly free the memory allocated with `stackalloc`. A stack allocated memory block isn't subject to [garbage collection](../../../standard/garbage-collection/index.md) and doesn't have to be pinned with a [`fixed` statement](../statements/fixed.md). +A `stackalloc` expression allocates a block of memory on the stack. A stack-allocated memory block created during the method execution is automatically discarded when that method returns. You can't explicitly free the memory allocated by `stackalloc`. A stack-allocated memory block isn't subject to [garbage collection](../../../standard/garbage-collection/index.md) and doesn't need to be pinned by a [`fixed` statement](../statements/fixed.md). [!INCLUDE[csharp-version-note](../includes/initial-version.md)] @@ -19,7 +19,7 @@ You can assign the result of a `stackalloc` expression to a variable of one of t :::code language="csharp" source="snippets/shared/StackallocOperator.cs" id="AssignToSpan"::: - You don't need to use an [unsafe](../keywords/unsafe.md) context when you assign a stack allocated memory block to a or variable. + You don't need to use an [unsafe](../keywords/unsafe.md) context when you assign a stack-allocated memory block to a or variable. When you work with those types, you can use a `stackalloc` expression in [conditional](conditional-operator.md) or assignment expressions, as the following example shows: @@ -30,19 +30,22 @@ You can assign the result of a `stackalloc` expression to a variable of one of t :::code language="csharp" source="snippets/shared/StackallocOperator.cs" id="Nested"::: > [!NOTE] - > Use or types to work with stack allocated memory whenever possible. + > Use or types to work with stack-allocated memory whenever possible. - A [pointer type](../unsafe-code.md#pointer-types), as the following example shows: :::code language="csharp" source="snippets/shared/StackallocOperator.cs" id="AssignToPointer"::: - As the preceding example shows, you must use an `unsafe` context when you work with pointer types. + You must use an `unsafe` context when you work with pointer types. + + > [!NOTE] + > The [memory safety](../unsafe-code.md#the-updated-memory-safety-model-preview) preview feature available in C# 15 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 is thrown. To avoid that exception, follow these rules: -- Limit the amount of memory you allocate with `stackalloc`. For example, if the intended buffer size is below a certain limit, you allocate the memory on the stack; otherwise, use an array of the required length, as the following code shows: +- Limit the amount of memory you allocate by using `stackalloc`. For example, if the intended buffer size is below a certain limit, allocate the memory on the stack. Otherwise, use an array of the required length, as the following code shows: :::code language="csharp" source="snippets/shared/StackallocOperator.cs" id="LimitStackalloc"::: @@ -51,7 +54,7 @@ The amount of memory available on the stack is limited. If you allocate too much - Avoid using `stackalloc` inside loops. Allocate the memory block outside a loop and reuse it inside the loop. -The content of the newly allocated memory is undefined. You should initialize it, either with a `stackalloc` initializer, or a method like before it's used. +The content of the newly allocated memory is undefined. You should initialize it before it's used, either with a `stackalloc` initializer or a method like . > [!IMPORTANT] > Not initializing memory allocated by `stackalloc` is an important difference from the `new` operator. Memory allocated by using the `new` operator is initialized to the 0 bit pattern. @@ -60,7 +63,7 @@ You can use array initializer syntax to define the content of the newly allocate :::code language="csharp" source="snippets/shared/StackallocOperator.cs" id="StackallocInit"::: -In expression `stackalloc T[E]`, `T` must be an [unmanaged type](../builtin-types/unmanaged-types.md) and `E` must evaluate to a non-negative [int](../builtin-types/integral-numeric-types.md) value. When you use the [collection expression](./collection-expressions.md) syntax to initialize the span, the compiler can use stack allocated storage for a span if it doesn't violate ref safety. +In expression `stackalloc T[E]`, `T` must be an [unmanaged type](../builtin-types/unmanaged-types.md) and `E` must evaluate to a non-negative [int](../builtin-types/integral-numeric-types.md) value. When you use the [collection expression](./collection-expressions.md) syntax to initialize the span, the compiler can use stack-allocated storage for a span if it doesn't violate ref safety. ## Security diff --git a/docs/csharp/language-reference/snippets/memory-safety/Program.cs b/docs/csharp/language-reference/snippets/memory-safety/Program.cs new file mode 100644 index 0000000000000..73fa4a9ef4bc8 --- /dev/null +++ b/docs/csharp/language-reference/snippets/memory-safety/Program.cs @@ -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])); diff --git a/docs/csharp/language-reference/snippets/memory-safety/Relaxations.cs b/docs/csharp/language-reference/snippets/memory-safety/Relaxations.cs new file mode 100644 index 0000000000000..9add8867496c0 --- /dev/null +++ b/docs/csharp/language-reference/snippets/memory-safety/Relaxations.cs @@ -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 +{ + // + public static void CreatePointer() + { + int value = 42; + // Creating a pointer doesn't require an unsafe context. + int* pointer = &value; + int** pointerToPointer = &pointer; + } + // + + // + public static void PinArray(int[] numbers) + { + // The fixed statement no longer requires an unsafe context. + fixed (int* first = numbers) + { + int* current = first; + } + } + // + + // + public static void AllocateOnStack() + { + // Converting a stackalloc to a pointer no longer requires an unsafe context. + int* buffer = stackalloc int[10]; + } + // + + // + public static int SizeOfStruct() + { + // sizeof of any unmanaged type no longer requires an unsafe context. + return sizeof(System.Guid); + } + // + + // + 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; + } + } + } + // +} diff --git a/docs/csharp/language-reference/snippets/memory-safety/memory-safety.csproj b/docs/csharp/language-reference/snippets/memory-safety/memory-safety.csproj new file mode 100644 index 0000000000000..e661421e0366c --- /dev/null +++ b/docs/csharp/language-reference/snippets/memory-safety/memory-safety.csproj @@ -0,0 +1,12 @@ + + + + Exe + net11.0 + enable + enable + preview + true + + + diff --git a/docs/csharp/language-reference/statements/fixed.md b/docs/csharp/language-reference/statements/fixed.md index c2a50520f8b1c..85f59ef59eb72 100644 --- a/docs/csharp/language-reference/statements/fixed.md +++ b/docs/csharp/language-reference/statements/fixed.md @@ -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" @@ -18,8 +18,9 @@ The `fixed` statement prevents the [garbage collector](../../../standard/garbage [!INCLUDE[csharp-version-note](../includes/initial-version.md)] > [!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. - +> To use the `fixed` statement, compile the code with the [**AllowUnsafeBlocks**](../compiler-options/language.md#allowunsafeblocks) compiler option. +> +> The [memory safety](../unsafe-code.md#the-updated-memory-safety-model-preview) preview feature available in C# 15 feature lets you use `fixed` outside an `unsafe` context, but pointer indirection and other operations that access pinned memory still require an `unsafe` context. 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. diff --git a/docs/csharp/language-reference/toc.yml b/docs/csharp/language-reference/toc.yml index 8f7dc25b827ef..4bc8365069aa9 100644 --- a/docs/csharp/language-reference/toc.yml +++ b/docs/csharp/language-reference/toc.yml @@ -135,6 +135,8 @@ items: href: ./keywords/override.md - name: readonly href: ./keywords/readonly.md + - name: safe + href: ./keywords/safe.md - name: sealed href: ./keywords/sealed.md - name: static diff --git a/docs/csharp/language-reference/unsafe-code.md b/docs/csharp/language-reference/unsafe-code.md index 201b19368a789..df901da6e488c 100644 --- a/docs/csharp/language-reference/unsafe-code.md +++ b/docs/csharp/language-reference/unsafe-code.md @@ -1,7 +1,8 @@ --- 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). -ms.date: 01/16/2026 +description: Learn about unsafe code, pointers, and function pointers. C# uses an unsafe context for operations that access unmanaged memory or invoke function pointers (unmanaged delegates). +ms.date: 06/17/2026 +ai-usage: ai-assisted f1_keywords: - "functionPointer_CSharpKeyword" helpviewer_keywords: @@ -19,19 +20,39 @@ Most of the C# code you write is verifiably safe code. *Verifiably safe code* me [!INCLUDE[csharp-version-note](./includes/initial-version.md)] -C# supports an [`unsafe`](keywords/unsafe.md) context, in which you can write *unverifiable* code. In an `unsafe` context, code can use pointers, allocate and free blocks of memory, and call methods by using function pointers. Unsafe code in C# isn't necessarily dangerous; it's just code whose safety can't be verified. +C# also supports an [`unsafe`](keywords/unsafe.md) context, in which you can write *unverifiable* code. Unsafe code isn't necessarily dangerous; it's just code whose safety can't be verified by .NET tools. You use unsafe code to call native functions that require pointers, and in some cases to improve performance through direct memory access that avoids array-bounds checks. Unsafe code also introduces security and stability risks. To compile code that contains an `unsafe` context, add the [**AllowUnsafeBlocks**](compiler-options/language.md#allowunsafeblocks) compiler option. -Unsafe code has the following properties: - -- You can define methods, types, and code blocks as unsafe. -- In some cases, unsafe code can increase an application's performance by enabling direct memory access through pointers to avoid array bounds checks. -- You use unsafe code to call native functions that require pointers. -- Using unsafe code introduces security and stability risks. -- You must add the [**AllowUnsafeBlocks**](compiler-options/language.md#allowunsafeblocks) compiler option to compile the code that contains unsafe blocks. +C# defines two models for what counts as unsafe code: the original model and an updated memory safety model that's in preview in C# 15 and .NET 11. For information about how the two models differ, see [Two models for unsafe code](#two-models-for-unsafe-code). For information about best practices for unsafe code in C#, see [Unsafe code best practices](../../standard/unsafe-code/best-practices.md). -## Pointer types +## Two models for unsafe code + +C# defines two models for unsafe code. The model in effect determines which operations require an `unsafe` context and how the `unsafe` modifier on a member affects callers. + +- **Original unsafe model**: The `unsafe` context covers the existence of *pointer features*. You declare a pointer type, take the address of a variable, dereference a pointer, convert a `stackalloc` expression to a pointer, or apply `sizeof` to an arbitrary type only inside an `unsafe` context. (A `stackalloc` expression assigned to a `Span` or `ReadOnlySpan` is allowed in safe code.) The `unsafe` modifier on a type, a member, or a block establishes that context but places no obligation on callers. C# 1.0 introduced this model, and it remains the default. +- **Updated memory safety model**: The `unsafe` context covers the operations that *access memory the runtime doesn't manage*. The existence of a pointer isn't unsafe; the dereference of a pointer is. The `unsafe` modifier on a member becomes a contract that propagates the obligation to audit safety to the caller. This model is in preview in C# 15 and .NET 11. + +The following table compares which operations require an `unsafe` context in each model. + +| Operation | Original model | Updated model | +|--------------------------------------------------------------------------------|-----------------------|----------------------| +| Declare a pointer type or take an address with `&` | Requires `unsafe` | Allowed in safe code | +| The [`fixed`](statements/fixed.md) statement | Requires `unsafe` | Allowed in safe code | +| Convert a [`stackalloc`](operators/stackalloc.md) expression to a pointer | Requires `unsafe` | Allowed in safe code | +| The [`sizeof`](operators/sizeof.md) operator on any unmanaged type | Requires `unsafe` | Allowed in safe code | +| Pointer indirection (`*p`), member access (`p->m`), or element access (`p[i]`) | Requires `unsafe` | Requires `unsafe` | +| Function pointer invocation | Requires `unsafe` | Requires `unsafe` | +| Element access on a fixed-size buffer | Requires `unsafe` | Requires `unsafe` | +| Call a member marked `unsafe` | No caller requirement | Requires `unsafe` | + +To try the updated model, use the .NET 11 SDK (in preview) and set the [`LangVersion`](compiler-options/language.md#langversion) compiler option to `preview`. The pointer relaxations apply whenever you compile with the C# 15 compiler and the `preview` language version. The full enforcement, including caller obligations and the assembly opt-in, is still under development. For more information, see [The updated memory safety model (preview)](#the-updated-memory-safety-model-preview). + +## The original unsafe model + +In the original model, the `unsafe` keyword establishes an unsafe context on a type, a member, or a block, and that context unlocks the pointer features described in the following sections. The `unsafe` modifier changes only what the marked code can do; it places no requirement on callers. To compile any of these examples, set the [**AllowUnsafeBlocks**](compiler-options/language.md#allowunsafeblocks) compiler option. + +### 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: @@ -101,27 +122,25 @@ The following example converts an `int*` to a `byte*`. Notice that the pointer p :::code language="csharp" source="snippets/unsafe-code/Conversions.cs" ID="Conversion"::: -## Fixed-size buffers +### Fixed-size buffers + +Arrays are reference types, so in safe code, a struct field that's an array stores only a reference to the array's elements, not the elements themselves. The size of the following `struct` doesn't depend on the number of elements in the array, because `pathName` is a reference: + +:::code language="csharp" source="snippets/unsafe-code/FixedKeywordExamples.cs" ID="6"::: -Use the `fixed` keyword to create a buffer with a fixed-size array in a data structure. Fixed-size buffers are useful when you write methods that interoperate with data sources from other languages or platforms. The fixed-size buffer can take any attributes or modifiers that are allowed for regular struct members. The only restriction is that the array type must be `bool`, `byte`, `char`, `short`, `int`, `long`, `sbyte`, `ushort`, `uint`, `ulong`, `float`, or `double`. +To store the array's contents inside the struct itself, use the `fixed` keyword to declare a *fixed-size buffer*. The `fixed` keyword requires an [`unsafe`](keywords/unsafe.md) context. Fixed-size buffers are useful when you write methods that interoperate with data sources from other languages or platforms. A fixed-size buffer can take any attributes or modifiers that are allowed for regular struct members. The only restriction is that the array type must be `bool`, `byte`, `char`, `short`, `int`, `long`, `sbyte`, `ushort`, `uint`, `ulong`, `float`, or `double`: ```csharp private fixed char name[30]; ``` -In safe code, a C# struct that contains an array doesn't contain the array elements. The struct contains a reference to the elements instead. You can embed an array of fixed size in a [struct](builtin-types/struct.md) when it's used in an [unsafe](keywords/unsafe.md) code block. - -The size of the following `struct` doesn't depend on the number of elements in the array, since `pathName` is a reference: - -:::code language="csharp" source="snippets/unsafe-code/FixedKeywordExamples.cs" ID="6"::: - -A struct can contain an embedded array in unsafe code. In the following example, the `fixedBuffer` array has a fixed size. You use a [`fixed` statement](statements/fixed.md) to get a pointer to the first element. You access the elements of the array through this pointer. The `fixed` statement pins the `fixedBuffer` instance field to a specific location in memory. +In the following example, the `fixedBuffer` array has a fixed size. You use a [`fixed` statement](statements/fixed.md) to get a pointer to the first element, then access the elements of the array through that pointer. The `fixed` statement pins the `fixedBuffer` instance field to a specific location in memory: :::code language="csharp" source="snippets/unsafe-code/FixedKeywordExamples.cs" ID="7"::: The size of the 128 element `char` array is 256 bytes. Fixed-size [char](builtin-types/char.md) buffers always take 2 bytes per character, regardless of the encoding. This array size is the same even when char buffers are marshaled to API methods or structs with `CharSet = CharSet.Auto` or `CharSet = CharSet.Ansi`. For more information, see . -The preceding example demonstrates accessing `fixed` fields without pinning. Another common fixed-size array is the [bool](builtin-types/bool.md) array. The elements in a `bool` array are always 1 byte in size. `bool` arrays aren't appropriate for creating bit arrays or buffers. +The preceding example demonstrates accessing `fixed` fields without pinning. Another common fixed-size array is the [bool](builtin-types/bool.md) array. The elements in a `bool` array are always 1 byte in size. `bool` arrays aren't appropriate for creating bit arrays or buffers. Fixed-size buffers are compiled with the , which instructs the common language runtime (CLR) that a type contains an unmanaged array that can potentially overflow. Memory allocated by using [stackalloc](operators/stackalloc.md) also automatically enables buffer overrun detection features in the CLR. The preceding example shows how a fixed-size buffer could exist in an `unsafe struct`. @@ -157,17 +176,7 @@ Fixed-size buffers differ from regular arrays in the following ways: - They're always vectors, or one-dimensional arrays. - The declaration must include the length, such as `fixed char id[8]`. You can't use `fixed char id[]`. -## How to use pointers to copy an array of bytes - -The following example uses pointers to copy bytes from one array to another. - -This example uses the [unsafe](keywords/unsafe.md) keyword, which enables you to use pointers in the `Copy` method. The [fixed](statements/fixed.md) statement declares pointers to the source and destination arrays. The `fixed` statement *pins* the location of the source and destination arrays in memory so that garbage collection doesn't move the arrays. The `fixed` block pins the memory blocks for the arrays in the scope of the block. Because the `Copy` method in this example uses the `unsafe` keyword, you must compile it by using the [**AllowUnsafeBlocks**](compiler-options/language.md#allowunsafeblocks) compiler option. - -This example accesses the elements of both arrays by using indices rather than a second unmanaged pointer. The declaration of the `pSource` and `pTarget` pointers pins the arrays. - -:::code language="csharp" source="snippets/unsafe-code/FixedKeywordExamples.cs" ID="8"::: - -## Function pointers +### Function pointers C# provides [`delegate`](builtin-types/reference-types.md#the-delegate-type) types to define safe function pointer objects. Invoking a delegate involves instantiating a type derived from and making a virtual method call to its `Invoke` method. This virtual call uses the `callvirt` IL instruction. In performance critical code paths, using the `calli` IL instruction is more efficient. @@ -193,10 +202,230 @@ You can specify the calling convention for a `delegate*` by using the keywords ` You can learn more about function pointers in the [Function pointers](~/_csharpstandard/standard/unsafe-code.md#2433-function-pointers) section of the C# language specification. +### Example: Use pointers to copy an array of bytes + +The following example uses pointers to copy bytes from one array to another. + +This example uses the [`unsafe`](keywords/unsafe.md) keyword, which enables you to use pointers in the `Copy` method. The [`fixed`](statements/fixed.md) statement declares pointers to the source and destination arrays. The `fixed` statement *pins* the location of the source and destination arrays in memory so that garbage collection doesn't move the arrays. The `fixed` block pins the memory blocks for the arrays in the scope of the block. Because the `Copy` method in this example uses the `unsafe` keyword, you must compile it by using the [**AllowUnsafeBlocks**](compiler-options/language.md#allowunsafeblocks) compiler option. + +This example accesses the elements of both arrays by using indices rather than a second unmanaged pointer. The declaration of the `pSource` and `pTarget` pointers pins the arrays. + +:::code language="csharp" source="snippets/unsafe-code/FixedKeywordExamples.cs" ID="8"::: + +## The updated memory safety model (preview) + +> [!IMPORTANT] +> The updated memory safety model is a preview feature in C# 15 and .NET 11. It continues to evolve based on feedback during the preview releases. To try the model, use the .NET 11 (preview) SDK and set the [`LangVersion`](compiler-options/language.md#langversion) compiler option to `preview`. The compiler in .NET 11 Preview 5 implements the pointer relaxations but doesn't yet enforce the caller obligations, the assembly opt-in, or the `safe` keyword. For the full design, see the [memory safety feature specification](~/_csharplang/proposals/unsafe-evolution.md). + +The updated model separates two things the original model treats as one: the *existence* of pointer code and the *propagation* of safety obligations to callers. Marking a member `unsafe` no longer just permits pointers in its body; it makes the member *caller-unsafe*, so every caller must either propagate that obligation or discharge it behind a validated, safe-callable boundary. To support that separation, the model also narrows the unsafe context: the existence of a pointer isn't unsafe, only the operations that access memory the runtime doesn't manage. The narrowing lets you hold, pass, and return pointers in safe code, while `unsafe` marks the operations and members that can actually violate memory safety. + +### Caller-unsafe members + +In the original model, the `unsafe` modifier on a member only allows pointers in the member's signature and body. It doesn't inform callers about safety. The updated model gives the modifier meaning for callers. When you mark a member `unsafe`, the compiler treats it as *caller-unsafe* (also called *requires-unsafe*): every caller must invoke it from an `unsafe` context, and the obligation to audit safety moves to that caller. + +The `unsafe` modifier on a member signature no longer establishes an unsafe context for the body. The two roles split: + +- The `unsafe` modifier on the signature propagates the obligation to callers. +- An inner `unsafe` block scopes the operations that access unmanaged memory. + +In the following preview mockup, `ReadInt32` is caller-unsafe. The signature carries the `unsafe` modifier, and an inner `unsafe` block wraps the dereference: + +```csharp +// Preview: illustrates the updated model, which the current compiler doesn't fully enforce yet. +public static unsafe int ReadInt32(byte* source) +{ + unsafe + { + return *(int*)source; + } +} +``` + +A caller wraps the call in its own `unsafe` block: + +```csharp +// Preview +unsafe +{ + int value = ReadInt32(buffer); +} +``` + +The updated model also tightens a few related rules: + +- The `unsafe` modifier produces an error on a type declaration, a static constructor, and a finalizer, because the modifier has no caller to inform. +- Delegates can't be `unsafe`, because a delegate is type-shaped. +- A type whose parameterless constructor is `unsafe` doesn't satisfy the `new()` constraint. + +### Operations that require an unsafe context + +Operations that access the pointed-to memory 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"::: + +### Relaxed operations + +Operations that don't access pointed-to memory 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. + +### Discharge caller-unsafe obligations + +A member that calls a caller-unsafe operation has two choices: propagate the obligation, or discharge it. + +- **Propagate**: Mark your own member `unsafe`. The obligation passes to your callers. Use propagation when you can't fully validate the obligation yourself. +- **Discharge**: Leave your member's signature safe. Validate the obligation inside the member, usually with runtime guards, then perform the unsafe operation in an inner `unsafe` block. A member that contains an inner `unsafe` block but doesn't mark its own signature `unsafe` is an *unsafe boundary*: it turns unsafe code into a safe-callable surface. + +The following preview mockup validates its input with a guard, pins a managed array, and reads through the pointer. Callers don't need an `unsafe` context, because the method discharges the obligation: + +```csharp +// Preview +public static int SumBytes(byte[] source) +{ + ArgumentNullException.ThrowIfNull(source); + + fixed (byte* first = source) + { + unsafe + { + // SAFETY: the null check and source.Length bound every read to the pinned array. + int total = 0; + for (int i = 0; i < source.Length; i++) + { + total += first[i]; + } + + return total; + } + } +} +``` + +The null check and the array length rule out the inputs that would let a read run past the buffer, so the dereference inside the `unsafe` block is sound. The method leaves no residual obligation, so it exposes a safe-callable signature. + +### Safety documentation + +A caller-unsafe member should document what the caller must guarantee. The updated model encourages two complementary comment styles: + +- A `/// ` documentation block above the signature states the formal contract: the conditions a caller must satisfy. An analyzer can flag a caller-unsafe member that's missing one. +- A `// SAFETY:` comment inside an `unsafe` block records why the operation is sound at that spot, for the developers and auditors who read the body. + +The following preview mockup shows both styles on a caller-unsafe `ReadByte` method: + +```csharp +// Preview +/// Reads a single byte from unmanaged memory. +/// +/// The sum of and must address a byte +/// the caller is permitted to read. +/// +public static unsafe byte ReadByte(IntPtr ptr, int offset) +{ + byte* address = (byte*)ptr; + unsafe + { + // SAFETY: relies on the caller obligation stated in the block. + return address[offset]; + } +} +``` + +The `/// ` block tells you the contract.The contract belongs in the documentation where every caller and reviewer sees it. + +### Unsafe fields + +Use the `unsafe` modifier for a field when its declared type doesn't express contracts that the enclosing type maintains and other code depends on. The unsafety exists in the gap between what the type system sees and what the type promises. The modifier forces every write to the field into an `unsafe` block, which keeps the writes reviewable in one place. + +The clearest case is a field that holds a native pointer. The pointer doesn't declare how many bytes it addresses as a does, so the containing type maintains that information itself: + +```csharp +// Preview +public class NativeBuffer +{ + /// + /// Null, or points to a buffer of Length bytes. + /// + private unsafe byte* _pointer; + + public int Length { get; } + + public byte ReadAt(int index) + { + ArgumentOutOfRangeException.ThrowIfNegative(index); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Length); + unsafe + { + // SAFETY: the bounds checks confine the read to the buffer that _pointer addresses. + return _pointer[index]; + } + } +} +``` + +A `readonly unsafe` field pairs the contract with a built-in guard: `unsafe` names the invariant, and `readonly` prevents a write that could break it after construction. Marking a property or an event `unsafe` doesn't make its backing field caller-unsafe. In a struct with `[StructLayout(LayoutKind.Explicit)]`, you mark every field either `safe` or `unsafe`. + +### The safe keyword + +The updated model adds a [`safe`](keywords/safe.md) contextual keyword that attests a declaration is sound where the compiler requires you to make the choice explicit. + +An `extern` member calls into native code, so the compiler can't classify its safety. Under the updated model, you mark every `extern` declaration, including a `LibraryImport` partial method, either `safe` or `unsafe`: + +```csharp +// Preview +[LibraryImport("libc")] +internal static safe partial int getpid(); + +[LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)] +internal static unsafe partial nint strlen(byte* str); +``` + +`getpid` takes no parameters and returns a primitive, so the author attests that the call is safe and callers use it without ceremony. `strlen` takes a raw pointer that the native code dereferences, so the declaration is `unsafe` and propagates the obligation to callers. Omitting both modifiers is an error, which forces you to make the safety decision. A field in a struct with explicit layout uses the same rule. + +### Opt-in and cross-assembly behavior + +The updated model has two independent project-level switches: + +- A new opt-in property turns on the updated rules. When the property is off, the original rules apply. When it's on, `unsafe` on a member propagates to callers, and the compiler records the choice in the assembly with the attribute. +- The existing [**AllowUnsafeBlocks**](compiler-options/language.md#allowunsafeblocks) property gates every appearance of the `unsafe` keyword, including the inner blocks at call sites. It defaults to `false`, so a project at the default can't call any unsafe API. + +The two properties combine as follows: + +| Opt-in property | `AllowUnsafeBlocks` | Result | +|-----------------|---------------------|-----------------------------------------------------------------------------------------| +| On | Off (default) | The safest configuration. The project uses the updated model and allows no unsafe code. | +| On | On | The project uses the updated model and allows unsafe code. | +| Off | Off | The original model applies, and the project can't use pointer types. | +| Off | On | The original model applies, and the project can use pointer types. | + +Whether one assembly enforces the updated rules against another depends on which side opts in: + +- **Updated-model caller, updated-model callee**: The callee's `unsafe` markers travel through metadata. The caller wraps each call to a caller-unsafe member in an `unsafe` block. +- **Updated-model caller, original-model callee**: A compatibility mode treats any callee member with a pointer type in its signature as caller-unsafe, so the call site needs an enclosing `unsafe` block. This mode keeps a pointer-based API from silently losing its `unsafe` requirement. +- **Original-model caller, updated-model callee**: The original pointer rules still apply. A caller-unsafe member that has no pointer type in its signature becomes callable from safe code, because the original-model caller can't read the new markers. + ## C# language specification For more information, see the [Unsafe code](~/_csharpstandard/standard/unsafe-code.md) chapter of the [C# language specification](~/_csharpstandard/standard/README.md). +For the design of the updated memory safety model, see the [memory safety feature specification](~/_csharplang/proposals/unsafe-evolution.md). + ## See also - [Unsafe code best practices](../../standard/unsafe-code/best-practices.md) diff --git a/docs/csharp/language-reference/xmldoc/recommended-tags.md b/docs/csharp/language-reference/xmldoc/recommended-tags.md index 5e9449cc15db5..fcf299fd1baf6 100644 --- a/docs/csharp/language-reference/xmldoc/recommended-tags.md +++ b/docs/csharp/language-reference/xmldoc/recommended-tags.md @@ -90,6 +90,10 @@ helpviewer_keywords: - "cref [C#]" - "cross-references [C#], tags" - " C# XML tag" + - "safety" + - "" + - "safety C# XML tag" + - " C# XML tag" --- # Recommended XML tags for C# documentation comments @@ -234,6 +238,16 @@ The `` tag lets you specify which exceptions can be thrown. Apply thi The `` tag lets you describe the value that a property represents. When you add a property by using the code wizard in the Visual Studio .NET development environment, it adds a [``](#summary) tag for the new property. You manually add a `` tag to describe the value that the property represents. +### `` + +```xml +description +``` + +Use the `` tag to document the contract that a caller of a *caller-unsafe* member must satisfy. The [updated memory safety model](../unsafe-code.md#the-updated-memory-safety-model-preview), which is in preview in C# 15 and .NET 11, marks a member `unsafe` to push the obligation to audit safety onto the caller. The `` block states the conditions the caller must guarantee, so the contract lives in the documentation where every caller and reviewer sees it. You can also place a `` block on an `unsafe` field to record the invariant that the enclosing type maintains. + +The C# compiler doesn't recognize or process the `` tag. Like any custom tag, the compiler copies it verbatim to the output XML file. A memory safety analyzer might flag a caller-unsafe member that's missing a `` block, but the compiler itself doesn't enforce its presence or contents. For more information, see [Safety documentation](../unsafe-code.md#safety-documentation). + ## Format documentation output ### `` @@ -404,9 +418,9 @@ Recommendation: ``` - `filename`: The name of the XML file containing the documentation. You can qualify the file name with a path relative to the source code file. Enclose `filename` in single quotation marks (' '). -- `path`: The path of the tags in `filename` that leads to the XML comment to use. The path may include one or multiple attributes like `name`, but they are not required. The attributes may have values like `id`, but values are not required either. Enclose the path including possible attributes in single quotation marks (' '). +- `path`: The path of the tags in `filename` that leads to the XML comment to use. The path can include one or multiple attributes like `name`, but they're not required. The attributes can have values like `id`, but values aren't required either. Enclose the path including possible attributes in single quotation marks (' '). - `attribName`, `attribName1`: The names of optional attributes. -- `attribValue`, `attribValue1`: The optional values of the attributes. If no value is specified, any value will be accepted when searching for the comment in `filename`. Enclose the attribute value in quotation marks ("). +- `attribValue`, `attribValue1`: The optional values of the attributes. If you don't specify a value, any value is accepted when searching for the comment in `filename`. Enclose the attribute value in quotation marks ("). By using the `` tag, you can refer to comments in another file that describe the types and members in your source code. Including an external file is an alternative to placing documentation comments directly in your source code file. By putting the documentation in a separate file, you can apply source control to the documentation separately from the source code. One person can have the source code file checked out and someone else can have the documentation file checked out. The `` tag uses the XML XPath syntax. Refer to XPath documentation for ways to customize your `` use. @@ -487,7 +501,7 @@ The `href` attribute means a reference to a web page. You can use it to directly - `TResult`: The name of the type parameter. Enclose the name in quotation marks ("). -The `` tag should be used in the comment for a generic type or method declaration to describe a type parameter. Add a tag for each type parameter of the generic type or method. The text for the `` tag is displayed in IntelliSense. +Use the `` tag in the comment for a generic type or method declaration to describe a type parameter. Add a tag for each type parameter of the generic type or method. The text for the `` tag appears in IntelliSense. ### `` @@ -497,11 +511,11 @@ The `` tag should be used in the comment for a generic type or method - `TKey`: The name of the type parameter. Enclose the name in quotation marks ("). -Use this tag to enable consumers of the documentation file to format the word in some distinct way, for example in italics. +Use this tag to enable consumers of the documentation file to format the word in some distinct way, such as in italics. ### User-defined tags -All the tags outlined in this article represent those tags recognized by the C# compiler. However, a user is free to define their own tags. -Tools like Sandcastle bring support for extra tags like [``](https://ewsoftware.github.io/XMLCommentsGuide/html/81bf7ad3-45dc-452f-90d5-87ce2494a182.htm) and [``](https://ewsoftware.github.io/XMLCommentsGuide/html/4302a60f-e4f4-4b8d-a451-5f453c4ebd46.htm), +All the tags outlined in this article represent tags recognized by the C# compiler. However, you can define your own tags. +Tools like [Sandcastle](https://ewsoftware.github.io/XMLCommentsGuide/html/81bf7ad3-45dc-452f-90d5-87ce2494a182.htm) bring support for extra tags like [``](https://ewsoftware.github.io/XMLCommentsGuide/html/81bf7ad3-45dc-452f-90d5-87ce2494a182.htm) and [``](https://ewsoftware.github.io/XMLCommentsGuide/html/4302a60f-e4f4-4b8d-a451-5f453c4ebd46.htm), and even support [documenting namespaces](https://ewsoftware.github.io/XMLCommentsGuide/html/BD91FAD4-188D-4697-A654-7C07FD47EF31.htm). -Custom or in-house documentation generation tools can also be used with the standard tags, and multiple output formats from HTML to PDF can be supported. +You can also use custom or in-house documentation generation tools with the standard tags, and support multiple output formats from HTML to PDF. diff --git a/docs/csharp/misc/sorry-we-don-t-have-specifics-on-this-csharp-error.md b/docs/csharp/misc/sorry-we-don-t-have-specifics-on-this-csharp-error.md index da9a0638ad83c..3164f1fd71db8 100644 --- a/docs/csharp/misc/sorry-we-don-t-have-specifics-on-this-csharp-error.md +++ b/docs/csharp/misc/sorry-we-don-t-have-specifics-on-this-csharp-error.md @@ -272,7 +272,7 @@ f1_keywords: - "CS9357" - "CS9358" - "CS9359" -# unsafe evolution +# memory safety - "CS9360" - "CS9361" - "CS9362" diff --git a/docs/csharp/specification/toc.yml b/docs/csharp/specification/toc.yml index 953f89f85e37c..a9c0f285373b3 100644 --- a/docs/csharp/specification/toc.yml +++ b/docs/csharp/specification/toc.yml @@ -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 diff --git a/docs/csharp/whats-new/csharp-15.md b/docs/csharp/whats-new/csharp-15.md index 736b581925d18..176a6f0842cf6 100644 --- a/docs/csharp/whats-new/csharp-15.md +++ b/docs/csharp/whats-new/csharp-15.md @@ -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 @@ -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). @@ -107,8 +108,38 @@ 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. -