diff --git a/.github/workflows/check-for-build-warnings.yml b/.github/workflows/check-for-build-warnings.yml index 7cbc04683f30d..36e2f8f7c7960 100644 --- a/.github/workflows/check-for-build-warnings.yml +++ b/.github/workflows/check-for-build-warnings.yml @@ -16,7 +16,7 @@ jobs: pull-requests: write steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/cleanrepo-orphaned-articles.yml b/.github/workflows/cleanrepo-orphaned-articles.yml index ba9430c50f3d3..44f921ff0286e 100644 --- a/.github/workflows/cleanrepo-orphaned-articles.yml +++ b/.github/workflows/cleanrepo-orphaned-articles.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/cleanrepo-orphaned-images.yml b/.github/workflows/cleanrepo-orphaned-images.yml index 89f5a123e14d9..3e806d9bca740 100644 --- a/.github/workflows/cleanrepo-orphaned-images.yml +++ b/.github/workflows/cleanrepo-orphaned-images.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/cleanrepo-orphaned-includes.yml b/.github/workflows/cleanrepo-orphaned-includes.yml index 10995f09f6e85..27162a6458f2c 100644 --- a/.github/workflows/cleanrepo-orphaned-includes.yml +++ b/.github/workflows/cleanrepo-orphaned-includes.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/cleanrepo-orphaned-snippets.yml b/.github/workflows/cleanrepo-orphaned-snippets.yml index 7b469c9c4ca7f..d5a19d1acaa59 100644 --- a/.github/workflows/cleanrepo-orphaned-snippets.yml +++ b/.github/workflows/cleanrepo-orphaned-snippets.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/cleanrepo-redirect-hops.yml b/.github/workflows/cleanrepo-redirect-hops.yml index 1c2f561a2dc95..b62538bbcde9a 100644 --- a/.github/workflows/cleanrepo-redirect-hops.yml +++ b/.github/workflows/cleanrepo-redirect-hops.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/cleanrepo-relative-links.yml b/.github/workflows/cleanrepo-relative-links.yml index 7e772830e3689..77b8631f0ebb8 100644 --- a/.github/workflows/cleanrepo-relative-links.yml +++ b/.github/workflows/cleanrepo-relative-links.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/cleanrepo-replace-redirects.yml b/.github/workflows/cleanrepo-replace-redirects.yml index 2844722977fd6..c0d0c5fdc411d 100644 --- a/.github/workflows/cleanrepo-replace-redirects.yml +++ b/.github/workflows/cleanrepo-replace-redirects.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/dependabot-bot.yml b/.github/workflows/dependabot-bot.yml index 1f7f22efc7db5..827aaf72169eb 100644 --- a/.github/workflows/dependabot-bot.yml +++ b/.github/workflows/dependabot-bot.yml @@ -26,7 +26,7 @@ jobs: # Checkout the repo into the workspace within the VM steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 4b8fb809f2e9b..6eb9643ed202a 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/do-not-merge-label-check.yml b/.github/workflows/do-not-merge-label-check.yml index 5902f3e10884a..a3c132b8468ec 100644 --- a/.github/workflows/do-not-merge-label-check.yml +++ b/.github/workflows/do-not-merge-label-check.yml @@ -22,7 +22,7 @@ jobs: - 'DO NOT MERGE' steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/docs-verifier.yml b/.github/workflows/docs-verifier.yml index 35391c697f2f1..d15c502be1153 100644 --- a/.github/workflows/docs-verifier.yml +++ b/.github/workflows/docs-verifier.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/live-protection.yml b/.github/workflows/live-protection.yml index 8010bc959a15f..ec375e6b2f09a 100644 --- a/.github/workflows/live-protection.yml +++ b/.github/workflows/live-protection.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml index 5d41f4484c8b4..f6a17fc9655e9 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yml @@ -22,12 +22,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: DavidAnson/markdownlint-cli2-action@6b51ade7a9e4a75a7ad929842dd298a3804ebe8b # v23.1.0 + - uses: DavidAnson/markdownlint-cli2-action@ded1f9488f68a970bc66ea5619e13e9b52e601cd # v23.2.0 with: config: ".markdownlint-cli2.jsonc" globs: "**/*.md" diff --git a/.github/workflows/profanity-filter.yml b/.github/workflows/profanity-filter.yml index 1d368866db7da..981d857d06567 100644 --- a/.github/workflows/profanity-filter.yml +++ b/.github/workflows/profanity-filter.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/quest-bulk.yml b/.github/workflows/quest-bulk.yml index cebb076f57176..8ff472058aa5f 100644 --- a/.github/workflows/quest-bulk.yml +++ b/.github/workflows/quest-bulk.yml @@ -28,7 +28,7 @@ jobs: if: ${{ github.repository_owner == 'dotnet' }} steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/quest.yml b/.github/workflows/quest.yml index 38fa4288e8953..14bba373c29f3 100644 --- a/.github/workflows/quest.yml +++ b/.github/workflows/quest.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 3f915b49da0b1..9b17707f33909 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit @@ -71,6 +71,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3.29.5 + uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v3.29.5 with: sarif_file: results.sarif diff --git a/.github/workflows/snippets5000.yml b/.github/workflows/snippets5000.yml index fa044010e0c89..0546b9cc21a9a 100644 --- a/.github/workflows/snippets5000.yml +++ b/.github/workflows/snippets5000.yml @@ -31,7 +31,7 @@ jobs: steps: # Checkout the repository for the PR - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 4a3ef8e10f40f..e01dfce15fede 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.github/workflows/version-sweep.yml b/.github/workflows/version-sweep.yml index 4f7e3b0cc082b..1e623122f25c9 100644 --- a/.github/workflows/version-sweep.yml +++ b/.github/workflows/version-sweep.yml @@ -34,7 +34,7 @@ jobs: # Start the .NET version updater action # A composite of the .NET Version Sweeper and the .NET Upgrade Assistant - name: Harden Runner - uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1 with: egress-policy: audit diff --git a/.openpublishing.redirection.csharp.json b/.openpublishing.redirection.csharp.json index aa121a7e60ef0..7a7f642051219 100644 --- a/.openpublishing.redirection.csharp.json +++ b/.openpublishing.redirection.csharp.json @@ -40,10 +40,30 @@ "source_path_from_root": "/docs/csharp/misc/cs0221.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" }, + { + "source_path_from_root": "/docs/csharp/misc/cs0273.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/property-declaration-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0274.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/property-declaration-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0275.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/property-declaration-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0276.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/property-declaration-errors" + }, { "source_path_from_root": "/docs/csharp/misc/cs0410.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/delegate-function-pointer-diagnostics" }, + { + "source_path_from_root": "/docs/csharp/misc/cs0442.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/property-declaration-errors" + }, { "source_path_from_root": "/docs/csharp/misc/cs0463.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" @@ -52,10 +72,30 @@ "source_path_from_root": "/docs/csharp/misc/cs0543.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" }, + { + "source_path_from_root": "/docs/csharp/misc/cs0544.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/property-declaration-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0546.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/property-declaration-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0547.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/property-declaration-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0548.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/property-declaration-errors" + }, { "source_path_from_root": "/docs/csharp/misc/cs0594.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" }, + { + "source_path_from_root": "/docs/csharp/misc/cs0610.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/property-declaration-errors" + }, { "source_path_from_root": "/docs/csharp/misc/cs0644.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/delegate-function-pointer-diagnostics" @@ -228,6 +268,10 @@ "source_path_from_root": "/docs/csharp/misc/cs1599.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/delegate-function-pointer-diagnostics" }, + { + "source_path_from_root": "/docs/csharp/misc/cs1715.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/property-declaration-errors" + }, { "source_path_from_root": "/docs/csharp/misc/cs1958.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/delegate-function-pointer-diagnostics" diff --git a/docs/ai/evaluation/snippets/evaluate-ai-responses/TestAI.csproj b/docs/ai/evaluation/snippets/evaluate-ai-responses/TestAI.csproj index 19942460ab022..1e6bd8c12e814 100644 --- a/docs/ai/evaluation/snippets/evaluate-ai-responses/TestAI.csproj +++ b/docs/ai/evaluation/snippets/evaluate-ai-responses/TestAI.csproj @@ -11,14 +11,14 @@ - + - - - - - + + + + + diff --git a/docs/ai/how-to/snippets/access-data/Project.csproj b/docs/ai/how-to/snippets/access-data/Project.csproj index e68407845c1ca..fe6e64382364d 100644 --- a/docs/ai/how-to/snippets/access-data/Project.csproj +++ b/docs/ai/how-to/snippets/access-data/Project.csproj @@ -11,10 +11,10 @@ - - - - + + + + diff --git a/docs/ai/quickstarts/snippets/mcp-client/MinimalMCPClient.csproj b/docs/ai/quickstarts/snippets/mcp-client/MinimalMCPClient.csproj index 71a6eb7346cc2..b1954a6311556 100644 --- a/docs/ai/quickstarts/snippets/mcp-client/MinimalMCPClient.csproj +++ b/docs/ai/quickstarts/snippets/mcp-client/MinimalMCPClient.csproj @@ -9,9 +9,9 @@ - - - + + + diff --git a/docs/ai/quickstarts/snippets/prompt-completion/azure-openai/ExtensionsAzureOpenAI.csproj b/docs/ai/quickstarts/snippets/prompt-completion/azure-openai/ExtensionsAzureOpenAI.csproj index fa0271c61d218..d15bd0545bd0f 100644 --- a/docs/ai/quickstarts/snippets/prompt-completion/azure-openai/ExtensionsAzureOpenAI.csproj +++ b/docs/ai/quickstarts/snippets/prompt-completion/azure-openai/ExtensionsAzureOpenAI.csproj @@ -8,11 +8,11 @@ - + - - - + + + diff --git a/docs/ai/snippets/microsoft-extensions-ai/AI.Shared/AI.Shared.csproj b/docs/ai/snippets/microsoft-extensions-ai/AI.Shared/AI.Shared.csproj index c90f45c97df35..2d59d5b9df902 100644 --- a/docs/ai/snippets/microsoft-extensions-ai/AI.Shared/AI.Shared.csproj +++ b/docs/ai/snippets/microsoft-extensions-ai/AI.Shared/AI.Shared.csproj @@ -7,7 +7,7 @@ - + diff --git a/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.CacheResponses/ConsoleAI.CacheResponses.csproj b/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.CacheResponses/ConsoleAI.CacheResponses.csproj index d3fbfebce33c3..1527dd026aea3 100644 --- a/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.CacheResponses/ConsoleAI.CacheResponses.csproj +++ b/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.CacheResponses/ConsoleAI.CacheResponses.csproj @@ -9,7 +9,7 @@ - + diff --git a/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.StatelessStateful/ConsoleAI.StatelessStateful.csproj b/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.StatelessStateful/ConsoleAI.StatelessStateful.csproj index c831db999fdab..9bd560e7cc7d7 100644 --- a/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.StatelessStateful/ConsoleAI.StatelessStateful.csproj +++ b/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.StatelessStateful/ConsoleAI.StatelessStateful.csproj @@ -8,7 +8,7 @@ - + diff --git a/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.ToolCalling/ConsoleAI.ToolCalling.csproj b/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.ToolCalling/ConsoleAI.ToolCalling.csproj index 69e1dda850abc..bb845dfd3498c 100644 --- a/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.ToolCalling/ConsoleAI.ToolCalling.csproj +++ b/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.ToolCalling/ConsoleAI.ToolCalling.csproj @@ -8,7 +8,7 @@ - + diff --git a/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.UseTelemetry/ConsoleAI.UseTelemetry.csproj b/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.UseTelemetry/ConsoleAI.UseTelemetry.csproj index 44faa47bdf8d5..ddf701516d4ce 100644 --- a/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.UseTelemetry/ConsoleAI.UseTelemetry.csproj +++ b/docs/ai/snippets/microsoft-extensions-ai/ConsoleAI.UseTelemetry/ConsoleAI.UseTelemetry.csproj @@ -9,7 +9,7 @@ - + diff --git a/docs/ai/snippets/prompt-engineering/multi-turn-chat.csproj b/docs/ai/snippets/prompt-engineering/multi-turn-chat.csproj index 79767b88e3b56..acf7aec25579f 100644 --- a/docs/ai/snippets/prompt-engineering/multi-turn-chat.csproj +++ b/docs/ai/snippets/prompt-engineering/multi-turn-chat.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/core/compatibility/11.md b/docs/core/compatibility/11.md index 089c8e7daf3f4..b618fd15f4bb6 100644 --- a/docs/core/compatibility/11.md +++ b/docs/core/compatibility/11.md @@ -29,6 +29,7 @@ See [Breaking changes in ASP.NET Core 10](/aspnet/core/breaking-changes/10/overv | [DeflateStream and GZipStream write headers and footers for empty payload](core-libraries/11/deflatestream-gzipstream-empty-payload.md) | Behavioral change | | [Environment.TickCount made consistent with Windows timeout behavior](core-libraries/11/environment-tickcount-windows-behavior.md) | Behavioral change | | [MemoryStream maximum capacity updated and exception behavior changed](core-libraries/11/memorystream-max-capacity.md) | Behavioral change | +| [NamedPipeServerStream with PipeOptions.CurrentUserOnly tightens Unix socket file permissions](core-libraries/11/namedpipeserverstream-unix-permissions.md) | Behavioral change | | [Nullable.GetUnderlyingType throws for custom Type subclasses](core-libraries/11/nullable-getunderlyingtype-throws.md) | Behavioral change | | [API obsoletions with non-default diagnostic IDs (.NET 11)](core-libraries/11/obsolete-apis.md) | Source incompatible | | [TAR-reading APIs verify header checksums when reading](core-libraries/11/tar-checksum-validation.md) | Behavioral change | @@ -77,8 +78,15 @@ See [Breaking changes in EF Core 11](/ef/core/what-is-new/ef-core-11.0/breaking- |-------------------------------------------------------------------|-------------------| | [SslStream server-side AIA certificate downloads disabled by default](networking/11/sslstream-aia-downloads-disabled.md) | Behavioral change | +## .NET MAUI + +| Title | Type of change | +|-------------------------------------------------------------------|-------------------| +| [Minimum Android API level raised to 24](maui/11/android-minimum-api-level.md) | Behavioral change | + ## SDK and MSBuild | Title | Type of change | |-------------------------------------------------------------------|-------------------| | [mono launch target not set for .NET Framework apps](sdk/11/mono-launch-target-removed.md) | Behavioral change | +| [VSTest removes dependency on Newtonsoft.Json](sdk/11/vstest-removes-newtonsoft-json.md) | Binary/source incompatible | diff --git a/docs/core/compatibility/core-libraries/11/namedpipeserverstream-unix-permissions.md b/docs/core/compatibility/core-libraries/11/namedpipeserverstream-unix-permissions.md new file mode 100644 index 0000000000000..9e8e9b1819350 --- /dev/null +++ b/docs/core/compatibility/core-libraries/11/namedpipeserverstream-unix-permissions.md @@ -0,0 +1,71 @@ +--- +title: "Breaking change: NamedPipeServerStream with PipeOptions.CurrentUserOnly tightens Unix socket file permissions" +description: "Learn about the breaking change in .NET 11 where NamedPipeServerStream with PipeOptions.CurrentUserOnly sets the Unix socket file mode to 0600." +ms.date: 05/04/2026 +ai-usage: ai-assisted +--- + +# NamedPipeServerStream with PipeOptions.CurrentUserOnly tightens Unix socket file permissions + +To better align on-disk permissions with the documented intent of and with the Windows implementation, the underlying Unix domain socket file is now created with file permissions `0600` (read/write for the owning user only). Previously, the socket file inherited permissions from the process umask, and `CurrentUserOnly` only rejected cross-user connections at connect time without restricting who could open the socket file itself. + +## Version introduced + +.NET 11 Preview 4 + +## Previous behavior + +Previously, the socket file backing a was created with whatever permissions the process umask allowed (commonly `0644` or `0755`). Specifying didn't change the on-disk file mode. Other local users could `stat` and might be able to attempt a connection to the socket file, depending on the platform and effective permissions. Cross-user connection attempts were rejected at connect time by peer-credential checks, but the socket file itself could still be visible to other users and, on some Unix systems, might also be connectable at the operating system level. + +```csharp +using var server = new NamedPipeServerStream( + "mypipe", PipeDirection.InOut, 1, + PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly); + +// Mode reflected the process umask, for example UserRead | UserWrite | GroupRead | OtherRead. +UnixFileMode mode = File.GetUnixFileMode("/tmp/CoreFxPipe_mypipe"); +``` + +## New behavior + +Starting in .NET 11, when you specify , the socket file is `chmod`'d to `0600` immediately after `bind()`. Other local users (other than root) can no longer open or connect to the socket file at the operating system level. + +```csharp +using var server = new NamedPipeServerStream( + "mypipe", PipeDirection.InOut, 1, + PipeTransmissionMode.Byte, PipeOptions.CurrentUserOnly); + +// Always UserRead | UserWrite (0600). +UnixFileMode mode = File.GetUnixFileMode("/tmp/CoreFxPipe_mypipe"); +``` + +For the in-process shared server cache, where multiple `NamedPipeServerStream` instances use the same pipe name, the permission change is one-way (ratcheted): + +- If a `CurrentUserOnly` instance is created for a given pipe name, the socket file is tightened to `0600` at that point and stays `0600` for the remainder of that path's shared lifetime. +- A later instance for the same pipe name that doesn't specify `CurrentUserOnly` doesn't loosen the mode back. + +## Type of breaking change + +This change is a [behavioral change](../../categories.md#behavioral-change). + +## Reason for change + + is documented as restricting access to the current user. On Unix, however, the socket file was created with permissions derived from the process umask, so enforcement relied on peer-credential checks at connect time. This left the socket file discoverable by other local users, and on platforms that honor socket-node permission bits for `connect()`, it might also allow other users to attempt or make a connection before peer-credential checks rejected cross-user access. That behavior made Unix behavior inconsistent with the option's documented intent and with the Windows implementation. For more information, see [dotnet/runtime#127239](https://github.com/dotnet/runtime/pull/127239). + +## Recommended action + +Most callers benefit from the tighter permissions and require no action. + +On Linux and macOS, uses Unix domain sockets. If you rely on that socket file being visible or connectable to local users other than the owner, update your guidance and your app assumptions to reflect that now sets the socket file mode to `0600` at bind time. + +Removing doesn't guarantee cross-user access by itself. When you omit that option, the socket file still inherits permissions from the process umask, and other users also need enough access to the directory that contains the socket path. If you intend to allow cross-user access, verify the effective socket file mode and the directory permissions on the target system. + +To let the server accept connections from other local users—for example, from a helper process that runs under a different account, or from external tooling that probes the socket—stop passing . + +Also account for the in-process ratcheting behavior. If any `NamedPipeServerStream` instance for a given pipe name in the process specifies `CurrentUserOnly`, all later instances for that pipe name keep `0600` permissions on the socket file until the shared server entry is released. + +## Affected APIs + +- +- +- diff --git a/docs/core/compatibility/maui/11/android-minimum-api-level.md b/docs/core/compatibility/maui/11/android-minimum-api-level.md new file mode 100644 index 0000000000000..2171a72617406 --- /dev/null +++ b/docs/core/compatibility/maui/11/android-minimum-api-level.md @@ -0,0 +1,42 @@ +--- +title: "Breaking change: Minimum Android API level raised to 24" +description: "Learn about the breaking change in .NET 11 where the minimum supported Android API level has been raised from 21 to 24." +ms.date: 05/04/2026 +ai-usage: ai-assisted +--- + +# Minimum Android API level raised to 24 + +The minimum supported Android API level for .NET 11 has been raised from 21 (Android 5.0) to 24 (Android 7.0). + +## Version introduced + +.NET 11 Preview 4 + +## Previous behavior + +Previously, you could target devices running Android API 21 (Android 5.0 Lollipop) or newer. + +## New behavior + +Starting in .NET 11, devices running Android API 24 (Android 7.0 Nougat) or newer are supported. Apps built with .NET 11 can't be installed or run on devices running Android API 21, 22, or 23. + +## Type of breaking change + +This change is a [behavioral change](../../categories.md#behavioral-change). + +## Reason for change + +Migrating the Android runtime from Mono to CoreCLR requires Android API 24 or later. + +## Recommended action + +Set `SupportedOSPlatformVersion` to `24` in your project file so your app declares the correct minimum supported Android version to build analyzers and assembly metadata. + +If you set `android:minSdkVersion` manually in your Android manifest, raise that value to `24` too so it matches the project setting. + +Notify users on devices that run Android 5.x or 6.x that they won't be able to install new updates of your app. + +## Affected APIs + +None. diff --git a/docs/core/compatibility/sdk/11/vstest-removes-newtonsoft-json.md b/docs/core/compatibility/sdk/11/vstest-removes-newtonsoft-json.md new file mode 100644 index 0000000000000..8d07e00581751 --- /dev/null +++ b/docs/core/compatibility/sdk/11/vstest-removes-newtonsoft-json.md @@ -0,0 +1,89 @@ +--- +title: "Breaking change: VSTest removes dependency on Newtonsoft.Json" +description: "Learn about the breaking change in .NET 11 where the VSTest platform and Microsoft.NET.Test.SDK no longer depend on Newtonsoft.Json." +ms.date: 05/04/2026 +ai-usage: ai-assisted +--- + +# VSTest removes dependency on Newtonsoft.Json + +The VSTest platform and `Microsoft.NET.Test.SDK` NuGet package no longer depend on `Newtonsoft.Json`. On .NET, `System.Text.Json` is used instead. On .NET Framework, JSONite is used. + +## Version introduced + +.NET 11 Preview 4 + +## Previous behavior + +Previously, `Microsoft.NET.Test.SDK` brought a transitive dependency on `Newtonsoft.Json` into test projects. `Newtonsoft.Json` was available for compilation and at runtime without an explicit package reference. Projects that used `Newtonsoft.Json` types without declaring a direct dependency on the package compiled and ran successfully. + +Additionally, VSTest's internal communication utilities exposed `Newtonsoft.Json` types (such as `Newtonsoft.Json.Linq.JToken`) in its public API. + +## New behavior + +Starting in .NET 11, `Newtonsoft.Json` is no longer a transitive dependency of `Microsoft.NET.Test.SDK`. Projects that used `Newtonsoft.Json` types without a direct package reference fail to compile. Projects that excluded `Newtonsoft.Json` runtime assets and relied on the copy shipped with VSTest now fail at runtime with: + +``` +FileNotFoundException: Could not load 'Newtonsoft.Json' +``` + +Test extensions (such as data collectors or test adapters) that depended on `Newtonsoft.Json` being provided by VSTest also fail with: + +``` +Data collector 'SampleDataCollector' threw an exception during type loading, construction, or initialization: +System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, +Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'. +``` + +The `Newtonsoft.Json`-based types in `Microsoft.VisualStudio.TestPlatform.CommunicationUtilities` have been removed from the public API. + +## Type of breaking change + +This change can affect [source compatibility](../../categories.md#source-compatibility) and [binary compatibility](../../categories.md#binary-compatibility). + +## Reason for change + +To align the VSTest platform's dependencies with the .NET SDK and the broader .NET ecosystem, `Newtonsoft.Json` was removed as a dependency. To minimize unintended side effects on the code under test, the number of assemblies that VSTest adds to the test output folder is reduced. Test projects can bring exactly the dependencies they need at the versions they require. + +## Recommended action + +In all cases, the fix is to add an explicit `Newtonsoft.Json` package reference to any project that uses it: + +```xml + +``` + +### Test projects that fail to compile + +If your test project uses `Newtonsoft.Json` types (for example, `JsonConvert`, `JToken`, or `JObject`) without a direct package reference, add a `PackageReference` to `Newtonsoft.Json` in your test project file. + +### Tests that fail with FileNotFoundException at runtime + +If your project has a `PackageReference` for `Newtonsoft.Json` with `runtime` and previously relied on the copy shipped with VSTest, remove the `ExcludeAssets` restriction or add a separate direct reference: + +```xml + + + runtime + + + + +``` + +### Test extensions that fail with FileNotFoundException + +If you maintain or use a test extension (data collector or test adapter) that depended on `Newtonsoft.Json` being provided by VSTest, add `Newtonsoft.Json` as a direct dependency to the extension's project. + +## Affected APIs + +The following `Newtonsoft.Json`-based APIs were removed from `Microsoft.VisualStudio.TestPlatform.CommunicationUtilities`: + +- `Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Message.Payload` property (type `Newtonsoft.Json.Linq.JToken?`) +- `Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Serialization.DefaultTestPlatformContractResolver` +- `Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Serialization.TestCaseConverter` +- `Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Serialization.TestObjectConverter` +- `Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Serialization.TestPlatformContractResolver` +- `Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Serialization.TestResultConverter` +- `Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Serialization.TestRunStatisticsConverter` +- `Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.VersionedMessage` diff --git a/docs/core/compatibility/toc.yml b/docs/core/compatibility/toc.yml index ed732eb84f966..e8efd991e7d09 100644 --- a/docs/core/compatibility/toc.yml +++ b/docs/core/compatibility/toc.yml @@ -20,6 +20,8 @@ items: href: core-libraries/11/environment-tickcount-windows-behavior.md - name: MemoryStream maximum capacity updated and exception behavior changed href: core-libraries/11/memorystream-max-capacity.md + - name: NamedPipeServerStream with PipeOptions.CurrentUserOnly tightens Unix socket file permissions + href: core-libraries/11/namedpipeserverstream-unix-permissions.md - name: Nullable.GetUnderlyingType throws for custom Type subclasses href: core-libraries/11/nullable-getunderlyingtype-throws.md - name: API obsoletions with non-default diagnostic IDs @@ -54,10 +56,16 @@ items: items: - name: SslStream server-side AIA certificate downloads disabled by default href: networking/11/sslstream-aia-downloads-disabled.md + - name: .NET MAUI + items: + - name: Minimum Android API level raised to 24 + href: maui/11/android-minimum-api-level.md - name: SDK and MSBuild items: - name: mono launch target not set for .NET Framework apps href: sdk/11/mono-launch-target-removed.md + - name: VSTest removes dependency on Newtonsoft.Json + href: sdk/11/vstest-removes-newtonsoft-json.md - name: .NET 10 items: - name: Overview diff --git a/docs/core/diagnostics/ambient-metadata/snippets/buildmetadata-msbuild-azure/buildmetadata-msbuild-azure.csproj b/docs/core/diagnostics/ambient-metadata/snippets/buildmetadata-msbuild-azure/buildmetadata-msbuild-azure.csproj index ca91231476f5c..b9fbb855143ae 100644 --- a/docs/core/diagnostics/ambient-metadata/snippets/buildmetadata-msbuild-azure/buildmetadata-msbuild-azure.csproj +++ b/docs/core/diagnostics/ambient-metadata/snippets/buildmetadata-msbuild-azure/buildmetadata-msbuild-azure.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/docs/core/diagnostics/dotnet-trace.md b/docs/core/diagnostics/dotnet-trace.md index 2f5a53de22f71..dda2760a009fe 100644 --- a/docs/core/diagnostics/dotnet-trace.md +++ b/docs/core/diagnostics/dotnet-trace.md @@ -237,6 +237,9 @@ dotnet-trace collect | `gc-collect` | Tracks GC collections only at very low overhead. | | `database` | Captures ADO.NET and Entity Framework database commands. | + > [!TIP] + > The sampling rate used by `dotnet-sampled-thread-time` can be changed with the `DOTNET_EventPipeThreadSamplingRate` environment variable (value in milliseconds). This setting is process-global and affects all EventPipe sessions. See [Trace using environment variables](./eventpipe.md#trace-using-environment-variables) for details. + > [!NOTE] > In past versions of the dotnet-trace tool, the collect verb supported a profile called `cpu-sampling`. This profile was removed because the name was misleading. It sampled all threads regardless of their CPU usage. You can achieve a similar result now using `--profile dotnet-sampled-thread-time,dotnet-common`. If you need to match the former `cpu-sampling` behavior exactly, use `--profile dotnet-sampled-thread-time --providers "Microsoft-Windows-DotNETRuntime:0x14C14FCCBD:4"`. diff --git a/docs/core/diagnostics/eventpipe.md b/docs/core/diagnostics/eventpipe.md index 2c43045032866..7e4b8ce8434c2 100644 --- a/docs/core/diagnostics/eventpipe.md +++ b/docs/core/diagnostics/eventpipe.md @@ -84,6 +84,8 @@ However, you can use the following environment variables to set up an EventPipe * `DOTNET_EventPipeProcNumbers`: Set this to `1` to enable capturing processor numbers in EventPipe event headers. The default value is `0`. +* `DOTNET_EventPipeThreadSamplingRate`: Available in .NET 11 and later. Sets the interval, in milliseconds, for the EventPipe thread time sampling profiler. When set to `0` or omitted, the runtime uses its built-in default of 10 ms (~100 Hz). This setting is process-global and affects all EventPipe sessions, including on-demand traces started by tools such as [dotnet-trace](./dotnet-trace.md). Setting a large value reduces sampling overhead but also reduces the resolution of any trace collected during the process lifetime. + * `DOTNET_EventPipeConfig`: Sets up the EventPipe session configuration when starting an EventPipe session with `DOTNET_EnableEventPipe`. The syntax is as follows: diff --git a/docs/core/diagnostics/snippets/OTLP-Example/csharp/OTLP-Example.csproj b/docs/core/diagnostics/snippets/OTLP-Example/csharp/OTLP-Example.csproj index 97905f9e2af7e..a319672c52651 100644 --- a/docs/core/diagnostics/snippets/OTLP-Example/csharp/OTLP-Example.csproj +++ b/docs/core/diagnostics/snippets/OTLP-Example/csharp/OTLP-Example.csproj @@ -16,7 +16,7 @@ - + diff --git a/docs/core/diagnostics/snippets/exception-summary/exception-summary.csproj b/docs/core/diagnostics/snippets/exception-summary/exception-summary.csproj index fed7bf57041ad..3aaf59e22c5ec 100644 --- a/docs/core/diagnostics/snippets/exception-summary/exception-summary.csproj +++ b/docs/core/diagnostics/snippets/exception-summary/exception-summary.csproj @@ -8,7 +8,7 @@ - + diff --git a/docs/core/extensions/dependency-injection/snippets/console-ienumerable/console-di-ienumerable.csproj b/docs/core/extensions/dependency-injection/snippets/console-ienumerable/console-di-ienumerable.csproj index 3433c251e61cf..f78753e0f767b 100644 --- a/docs/core/extensions/dependency-injection/snippets/console-ienumerable/console-di-ienumerable.csproj +++ b/docs/core/extensions/dependency-injection/snippets/console-ienumerable/console-di-ienumerable.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/docs/core/extensions/snippets/async-state/csharp/IAsyncContextExample/IAsyncContextExample.csproj b/docs/core/extensions/snippets/async-state/csharp/IAsyncContextExample/IAsyncContextExample.csproj index 14d28aad7e826..456780193eaed 100644 --- a/docs/core/extensions/snippets/async-state/csharp/IAsyncContextExample/IAsyncContextExample.csproj +++ b/docs/core/extensions/snippets/async-state/csharp/IAsyncContextExample/IAsyncContextExample.csproj @@ -8,7 +8,7 @@ - + diff --git a/docs/core/extensions/snippets/logging/getting-started-logger-message/getting-started-logger-message.csproj b/docs/core/extensions/snippets/logging/getting-started-logger-message/getting-started-logger-message.csproj index 2e9c86c2ed99b..2e111ab6604fe 100644 --- a/docs/core/extensions/snippets/logging/getting-started-logger-message/getting-started-logger-message.csproj +++ b/docs/core/extensions/snippets/logging/getting-started-logger-message/getting-started-logger-message.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/docs/core/runtime-config/debugging-profiling.md b/docs/core/runtime-config/debugging-profiling.md index 8cf36fbd94f48..bdd35a89ce9b5 100644 --- a/docs/core/runtime-config/debugging-profiling.md +++ b/docs/core/runtime-config/debugging-profiling.md @@ -81,6 +81,8 @@ The following table compares perf maps and jit maps. | **runtimeconfig.json** | N/A | N/A | | **Environment variable** | `DOTNET_PerfMapEnabled` | `0` - disabled
`1` - perf maps and jit dumps both enabled
`2` - jit dumps enabled
`3` - perf maps enabled | +For EventPipe-related environment variables, see [Trace using environment variables](../diagnostics/eventpipe.md#trace-using-environment-variables). + ## Perf log markers - Enables or disables the specified signal to be accepted and ignored as a marker in the perf logs. diff --git a/docs/core/testing/snippets/order-unit-tests/csharp/MSTest.Project/MSTest.Project.csproj b/docs/core/testing/snippets/order-unit-tests/csharp/MSTest.Project/MSTest.Project.csproj index f82c098ab1c39..eb506827d0ef3 100644 --- a/docs/core/testing/snippets/order-unit-tests/csharp/MSTest.Project/MSTest.Project.csproj +++ b/docs/core/testing/snippets/order-unit-tests/csharp/MSTest.Project/MSTest.Project.csproj @@ -8,9 +8,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/docs/core/testing/snippets/order-unit-tests/csharp/NUnit.TestProject/NUnit.Project.csproj b/docs/core/testing/snippets/order-unit-tests/csharp/NUnit.TestProject/NUnit.Project.csproj index f89c6d9a72388..6338464b50011 100644 --- a/docs/core/testing/snippets/order-unit-tests/csharp/NUnit.TestProject/NUnit.Project.csproj +++ b/docs/core/testing/snippets/order-unit-tests/csharp/NUnit.TestProject/NUnit.Project.csproj @@ -8,10 +8,10 @@ - + - - + + all runtime; build; native; contentfiles; analyzers diff --git a/docs/core/testing/snippets/order-unit-tests/csharp/XUnit.TestProject/XUnit.Project.csproj b/docs/core/testing/snippets/order-unit-tests/csharp/XUnit.TestProject/XUnit.Project.csproj index ce0425af52565..df393bc2d8c18 100644 --- a/docs/core/testing/snippets/order-unit-tests/csharp/XUnit.TestProject/XUnit.Project.csproj +++ b/docs/core/testing/snippets/order-unit-tests/csharp/XUnit.TestProject/XUnit.Project.csproj @@ -8,7 +8,7 @@ - + all diff --git a/docs/core/testing/snippets/unit-testing-using-mstest/csharp/PrimeService.Tests/PrimeService.Tests.csproj b/docs/core/testing/snippets/unit-testing-using-mstest/csharp/PrimeService.Tests/PrimeService.Tests.csproj index 6855aec7a4e3b..e450c791a1b40 100644 --- a/docs/core/testing/snippets/unit-testing-using-mstest/csharp/PrimeService.Tests/PrimeService.Tests.csproj +++ b/docs/core/testing/snippets/unit-testing-using-mstest/csharp/PrimeService.Tests/PrimeService.Tests.csproj @@ -7,10 +7,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/docs/csharp/fundamentals/tutorials/choosing-types.md b/docs/csharp/fundamentals/tutorials/choosing-types.md new file mode 100644 index 0000000000000..983c7043b30de --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/choosing-types.md @@ -0,0 +1,176 @@ +--- +title: "Tutorial: Choose between tuples, records, structs, and classes" +description: "Learn when to use tuples, record classes, record structs, classes, and interfaces in C# by building a coffee shop example that highlights each type's strengths." +ms.topic: tutorial +ms.date: 04/30/2026 +ai-usage: ai-assisted +#customer intent: As a C# developer, I want to choose the right type for my data so that my code is clear, maintainable, and correct. +--- + +# Tutorial: Choose between tuples, records, structs, and classes + +> [!TIP] +> This article is part of the **Fundamentals** section, written for developers who know at least one programming language and are learning C#. If you're new to programming, start with [Get started](../../tour-of-csharp/index.yml). For a quick reference table, see [Choose which kind of type](../types/index.md#choose-which-kind-of-type). + +One of your first design decisions in any C# application is choosing which kind of type to create. Should a menu item be a `class` or a `record`? Should a quick calculation return a `tuple` or a named type? Each choice shapes how your code handles equality, mutability, and polymorphism. The wrong pick leads to boilerplate, bugs, or both. + +In this tutorial, you build a small coffee shop model that uses menu items, orders, sensor readings, and discount policies. You analyze the characteristics and determine the best C# type for each concept. Along the way, you learn to recognize the design pressures that point toward one type over another. + +In this tutorial, you: + +> [!div class="checklist"] +> +> * Recognize when a tuple is the right fit for returning multiple values. +> * Model immutable data with a record class and understand value-based equality. +> * Represent small, copyable data with a record struct. +> * Manage mutable state and behavior with a class. +> * Extend a class through inheritance to add or tighten rules. +> * Define shared capabilities across unrelated types with an interface. + +## Prerequisites + +- Install the [.NET SDK](https://dotnet.microsoft.com/download/dotnet). + +## Use a tuple for a temporary grouping + +The coffee shop needs a method that returns both the total number of orders and the revenue for the day. You could define a class or struct for that, but two values from one method don't always justify a new type. + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="TupleDemo"::: + +`GetDailySummary` returns an `(int TotalOrders, decimal Revenue)` **tuple**. The caller accesses each element by name or deconstructs both into local variables. You don't need a class or struct definition. + +### Why a tuple works best in this example + +A tuple works here because the grouping is local: one method produces it, and one caller consumes it. Named elements make the intent clear without the ceremony of a full type. If you find yourself passing the same tuple shape across multiple methods, that's a signal to promote it to a record or class. You'll see that evolution [later in this tutorial](#tuple--record-the-grouping-keeps-showing-up). For more detail on tuple syntax and capabilities, see [Tuple types](../types/tuples.md). + +## Use a record for immutable data + +Every coffee shop needs a menu. A menu item has a name, a price, and a nutritional note. Those values don't change once the item is listed. Two systems that both reference a "Latte at $4.50" should agree they're talking about the same thing, even if they created separate objects. + +Declare a positional record: + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="MenuItem"::: + +The compiler generates a constructor, deconstructor, `Equals`, `GetHashCode`, and `ToString` from that single line. Put the record to work: + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="RecordClassDemo"::: + +Two `MenuItem` instances with the same data are equal even though they're separate objects. That behavior illustrates value-based equality. The `with` expression creates a seasonal variant without mutating the original. + +A **record class** is the right fit when identity comes from data, not from object reference, and instances rarely change after creation. You get readable `ToString()` output, structural equality, and `with` support out of the box. For a deeper walkthrough, see [Records](../types/records.md) and the [records tutorial](records.md). + +## Use a record struct for small value types + +The coffee machine has a built-in thermometer that reports temperature readings. Each reading is tiny—a number and a unit—and gets copied into logs, alerts, and dashboards. You don't want a change in one copy to ripple through the others. + +Declare a record struct: + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="Measurement"::: + +Use the record struct: + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="RecordStructDemo"::: + +Assigning `temp` to `copy` creates an independent value. The `with` expression produces a new value without touching the original—the same pattern as a record class, but with copy-on-assign behavior instead of copy-by-reference. + +A **record struct** fits when the data is small (a few primitive fields) and copying is cheaper than heap allocation. You get value equality and `with` support just like a record class, with true value semantics underneath. Measurements, coordinates, and similar lightweight data are natural candidates. For more context, see [Records](../types/records.md) and [Structure types](../types/structs.md). + +## Use a class when you need mutable state and behavior + +When a customer walks up to the counter, the barista starts an order and adds items one at a time. The total grows, the status changes from "Pending" to "Ready," and two orders placed at the same time—even with identical items—are still distinct orders. + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="Order"::: + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="ClassDemo"::: + +The `Order` class tracks items, computes a running total, and exposes a settable `Status`. A **class** is the right tool here because the object carries mutable state that changes over its lifetime, behavior (methods) is central to the type's purpose, and identity matters—two orders with the same items are still distinct orders. For more detail, see [Classes, structs, and records](../types/classes.md). + +## Use inheritance when you need to extend a class + +The coffee shop starts catering events. A catering order is still an order—it has items and a total—but it also tracks a guest count and requires manager approval before the kitchen marks it ready. Rather than duplicating `Order`'s logic, derive a specialized class. + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="CateringOrder"::: + +`CateringOrder` reuses `AddItem` and `Total` from the base class. The `Status` override tightens the contract—calling `Status = "Ready"` without prior approval throws an exception: + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="InheritanceDemo"::: + +This single derived class illustrates three inheritance concepts: + +- **Added state**: `MinimumGuests` and `ApprovedBy` exist only on the derived class. +- **Added behavior**: `Approve` is new—base `Order` doesn't know about approvals. +- **Overridden behavior**: the `Status` setter enforces a business rule that the base class doesn't have. + +Inheritance fits when the new type *is a* specialized version of the base type and you need to reuse existing state and behavior while adding or tightening rules. A shared base class is more natural than an interface when the types share implementation, not just a contract. + +## Use an interface to define shared capabilities + +The coffee shop runs different promotions—happy hour, loyalty rewards, seasonal specials. The checkout process needs to apply whichever discount is active today, without knowing the specifics of each policy. You need a way to say "anything that can apply a discount" without tying checkout to a single class. + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="Interfaces"::: + +The `Checkout` method accepts any `IDiscountPolicy`, so you can introduce new policies without changing the checkout logic: + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="InterfaceDemo"::: + +An **interface** declares a contract—a set of members that any implementing type must provide. The interface works here because the discount types are unrelated (they don't share a base class), yet checkout needs to treat them uniformly. Interfaces also make testing easy: swap in a stub policy without touching production code. For more detail, see [Interfaces](../types/interfaces.md). + +## Evolve your type choices + +None of these decisions are permanent. In fact, you can change them easily before you release a library where breaking changes become a consideration. As requirements grow, promote a simple type to a richer one. Here are three common evolutions. + +### Tuple → record: the grouping keeps showing up + +The `GetDailySummary` tuple works fine inside one method, but once you start passing it to reports, dashboards, and tests, a named type pays for itself. Promote the tuple to a record and add computed properties: + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="DailySummary"::: + +Callers that previously deconstructed the tuple now get `ToString()` for free, value equality, and a natural place for derived data like `AverageTicket`: + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="EvolveTupleToRecord"::: + +### Struct → class: you need inheritance + +The shop's maintenance team asks for calibrated readings: a sensor value adjusted by an offset. The `Measurement` record struct is great for raw data, but structs don't support inheritance, so you can't derive a calibrated variant. Promote to a class hierarchy: + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="SensorReading"::: + +`CalibratedReading` inherits from `SensorReading` and overrides `Display()` to include the offset. This pattern isn't possible with a struct or record struct: + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="EvolveStructToClass"::: + +### Class → class + interface: you need polymorphism across types + +The `Order` class works well on its own, but once `CateringOrder` exists, checkout, reporting, and printing all need to handle *any* order without caring which concrete type it is. Extract an interface with the members that callers actually depend on: + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="IOrder"::: + +Both `Order` and `CateringOrder` already satisfy this contract. Now a single method handles either type: + +:::code language="csharp" source="./snippets/choosing-types/Program.cs" id="EvolveClassToInterface"::: + +Extracting the interface doesn't change `Order` or `CateringOrder`—it just makes their shared shape explicit, which also makes testing easier. + +## Quick decision guide + +Use this table as a starting point when you aren't sure which type to pick: + +| Question | Best fit | +|-------------------------------------------------|---------------| +| Returning a few values from one method? | Tuple | +| Immutable data where equality is by values? | Record class | +| Small, copyable value data with equality? | Record struct | +| Mutable state, behavior, or reference identity? | Class | +| Specialized version of an existing class? | Derived class | +| Shared capability across unrelated types? | Interface | + +If none of these fit neatly, consider combining types. For example, a class can implement an interface, and a record can be a struct. For the full comparison, see [Choose which kind of type](../types/index.md#choose-which-kind-of-type). + +## Related content + +- [Tuple types](../types/tuples.md) +- [Records](../types/records.md) +- [Structure types](../types/structs.md) +- [Classes, structs, and records](../types/classes.md) +- [Interfaces](../types/interfaces.md) +- [Choose which kind of type](../types/index.md#choose-which-kind-of-type) diff --git a/docs/csharp/fundamentals/tutorials/snippets/choosing-types/Program.cs b/docs/csharp/fundamentals/tutorials/snippets/choosing-types/Program.cs new file mode 100644 index 0000000000000..d959fd148f8cc --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/snippets/choosing-types/Program.cs @@ -0,0 +1,211 @@ +#!/usr/bin/env dotnet + +// +(int TotalOrders, decimal Revenue) GetDailySummary(int orders, decimal revenue) + => (orders, revenue); + +Console.WriteLine("=== Tuple: daily summary ==="); +var summary = GetDailySummary(42, 1234.50m); +Console.WriteLine($"Orders: {summary.TotalOrders}, Revenue: {summary.Revenue:F2}"); + +var (orders, revenue) = summary; +Console.WriteLine($"Deconstructed: {orders} orders, {revenue:F2}"); +// + +// +Console.WriteLine("\n=== Record class: MenuItem ==="); +var latte = new MenuItem("Latte", 4.50m, "Contains dairy"); +var latte2 = new MenuItem("Latte", 4.50m, "Contains dairy"); +var seasonal = latte with { Name = "Pumpkin Spice Latte", Price = 5.25m }; + +Console.WriteLine(latte); +Console.WriteLine(seasonal); +Console.WriteLine($"Same reference (latte vs latte2): {ReferenceEquals(latte, latte2)}"); +Console.WriteLine($"Value equal (latte vs latte2): {latte == latte2}"); +Console.WriteLine($"Value equal (latte vs seasonal): {latte == seasonal}"); +// + +// +Console.WriteLine("\n=== Record struct: Measurement ==="); +var temp = new Measurement(72.5, "°F"); +var copy = temp; + +copy = copy with { Value = 23.0, Unit = "°C" }; + +Console.WriteLine($"Original: {temp.Value}{temp.Unit}"); +Console.WriteLine($"Copy (converted): {copy.Value}{copy.Unit}"); +// + +// +Console.WriteLine("\n=== Class: Order ==="); +var order = new Order(); +order.AddItem("Latte", 4.50m); +order.AddItem("Croissant", 3.25m); +order.Status = "Ready"; + +Console.WriteLine(order); +// + +// +Console.WriteLine("\n=== Inheritance: CateringOrder ==="); +var catering = new CateringOrder(minimumGuests: 20); +catering.AddItem("Coffee (serves 20)", 45.00m); +catering.AddItem("Pastry platter", 60.00m); + +try +{ + catering.Status = "Ready"; +} +catch (InvalidOperationException ex) +{ + Console.WriteLine($"Blocked: {ex.Message}"); +} + +catering.Approve("Sam"); +catering.Status = "Ready"; +Console.WriteLine(catering); +// + +// +static decimal Checkout(decimal total, IDiscountPolicy policy) => policy.Apply(total); + +Console.WriteLine("\n=== Interface: discount policy ==="); +decimal subtotal = 12.00m; +Console.WriteLine($"Happy hour (20% off): {Checkout(subtotal, new HappyHourDiscount()):F2}"); +Console.WriteLine($"Loyalty ($1 off): {Checkout(subtotal, new LoyaltyDiscount()):F2}"); +// + +// +Console.WriteLine("\n=== Evolve: tuple -> record ==="); +var daily = new DailySummary(120, 525.75m); +Console.WriteLine(daily); +Console.WriteLine($"Average ticket: {daily.AverageTicket:F2}"); +// + +// +Console.WriteLine("\n=== Evolve: struct -> class ==="); +var raw = new SensorReading(72.5, "°F"); +var calibrated = new CalibratedReading(72.5, "°F", offset: -0.3); + +Console.WriteLine($"Raw: {raw.Display()}"); +Console.WriteLine($"Calibrated: {calibrated.Display()}"); +// + +// +Console.WriteLine("\n=== Evolve: class -> class + interface ==="); +static void PrintOrderSummary(IOrder o) => + Console.WriteLine($" {o.Total:F2} [{o.Status}]"); + +var walkIn = new Order(); +walkIn.AddItem("Mocha", 5.00m); +walkIn.Status = "Ready"; + +var banquet = new CateringOrder(minimumGuests: 50); +banquet.AddItem("Coffee service", 90.00m); +banquet.Approve("Alex"); +banquet.Status = "Ready"; + +Console.WriteLine("All orders:"); +foreach (IOrder o in new IOrder[] { walkIn, banquet }) + PrintOrderSummary(o); +// + +// +record class MenuItem(string Name, decimal Price, string NutritionalNote); +// + +// +record struct Measurement(double Value, string Unit); +// + +// +record class DailySummary(int TotalOrders, decimal Revenue) +{ + public decimal AverageTicket => TotalOrders > 0 ? Revenue / TotalOrders : 0m; +} +// + +// +class SensorReading(double value, string unit) +{ + public double Value { get; } = value; + public string Unit { get; } = unit; + + public virtual string Display() => $"{Value}{Unit}"; +} + +class CalibratedReading(double value, string unit, double offset) + : SensorReading(value, unit) +{ + public double Offset { get; } = offset; + + public override string Display() => $"{Value + Offset}{Unit} (offset {Offset:+0.0;-0.0})"; +} +// + +// +interface IOrder +{ + string Status { get; set; } + decimal Total { get; } +} +// + +// +class Order : IOrder +{ + public virtual string Status { get; set; } = "Pending"; + private readonly List<(string Name, decimal Price)> _items = []; + + public void AddItem(string name, decimal price) => _items.Add((name, price)); + + public decimal Total => _items.Sum(i => i.Price); + + public override string ToString() => + $"Order [{Status}]: {string.Join(", ", _items.Select(i => i.Name))} - Total: {Total:F2}"; +} +// + +// +class CateringOrder : Order +{ + public int MinimumGuests { get; } + public string? ApprovedBy { get; private set; } + + public CateringOrder(int minimumGuests) => MinimumGuests = minimumGuests; + + public void Approve(string manager) => ApprovedBy = manager; + + public override string Status + { + get => base.Status; + set + { + if (value == "Ready" && ApprovedBy is null) + throw new InvalidOperationException( + "A catering order requires manager approval before it can be marked ready."); + base.Status = value; + } + } + + public override string ToString() => + $"Catering [{Status}] for {MinimumGuests}+ guests, approved by: {ApprovedBy ?? "(none)"} - Total: {Total:F2}"; +} +// + +// +interface IDiscountPolicy +{ + decimal Apply(decimal total); +} + +class HappyHourDiscount : IDiscountPolicy +{ + public decimal Apply(decimal total) => total * 0.80m; +} + +class LoyaltyDiscount : IDiscountPolicy +{ + public decimal Apply(decimal total) => total - 1.00m; +} +// diff --git a/docs/csharp/language-reference/compiler-messages/property-declaration-errors.md b/docs/csharp/language-reference/compiler-messages/property-declaration-errors.md index a01e476b2c69f..e7437efc66830 100644 --- a/docs/csharp/language-reference/compiler-messages/property-declaration-errors.md +++ b/docs/csharp/language-reference/compiler-messages/property-declaration-errors.md @@ -3,14 +3,26 @@ title: Compiler Errors on property declarations description: Use this article to diagnose and correct compiler errors and warnings when you define properties in your types. f1_keywords: - "CS0200" + - "CS0273" + - "CS0274" + - "CS0275" + - "CS0276" + - "CS0442" + - "CS0544" - "CS0545" + - "CS0546" + - "CS0547" + - "CS0548" - "CS0571" + - "CS0610" - "CS0840" - "CS1014" - "CS1043" + - "CS1715" - "CS8050" - "CS8051" - "CS8053" + - "CS8080" - "CS8145" - "CS8147" - "CS8341" @@ -20,6 +32,11 @@ f1_keywords: - "CS8660" - "CS8661" - "CS8664" + - "CS8852" + - "CS8853" + - "CS8855" + - "CS8856" + - "CS8903" - "CS9029" - "CS9030" - "CS9031" @@ -41,14 +58,26 @@ f1_keywords: - "CS9273" helpviewer_keywords: - "CS0200" + - "CS0273" + - "CS0274" + - "CS0275" + - "CS0276" + - "CS0442" + - "CS0544" - "CS0545" + - "CS0546" + - "CS0547" + - "CS0548" - "CS0571" + - "CS0610" - "CS0840" - "CS1014" - "CS1043" + - "CS1715" - "CS8050" - "CS8051" - "CS8053" + - "CS8080" - "CS8145" - "CS8147" - "CS8341" @@ -58,6 +87,11 @@ helpviewer_keywords: - "CS8660" - "CS8661" - "CS8664" + - "CS8852" + - "CS8853" + - "CS8855" + - "CS8856" + - "CS8903" - "CS9029" - "CS9030" - "CS9031" @@ -77,7 +111,7 @@ helpviewer_keywords: - "CS9264" - "CS9266" - "CS9273" -ms.date: 12/11/2025 +ms.date: 04/30/2026 ai-usage: ai-assisted --- # Errors and warnings related to property declarations @@ -88,33 +122,50 @@ You can encounter the following errors related to property declarations: That's by design. The text closely matches the text of the compiler error / warning for SEO purposes. --> - [**CS0200**](#readonly-properties): *Property or indexer 'property' cannot be assigned to -- it is read only* -- [**CS0545**](#property-accessor-syntax): *'function' : cannot override because 'property' does not have an overridable get accessor* -- [**CS0571**](#property-accessor-syntax): *'function' : cannot explicitly call operator or accessor* -- [**CS0840**](#auto-implemented-properties): *'Property name' must declare a body because it is not marked abstract or extern. Automatically implemented properties must define both get and set accessors.* -- [**CS1014**](#auto-implemented-properties): *A get or set accessor expected* -- [**CS1043**](#property-accessor-syntax): *{ or ; expected* -- [**CS8050**](#property-initializers): *Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers* -- [**CS8051**](#property-initializers): *Auto-implemented properties must have get accessors* -- [**CS8053**](#property-initializers): *Instance properties in interfaces cannot have initializers* +- [**CS0273**](#accessor-accessibility): *The accessibility modifier of the 'accessor' accessor must be more restrictive than the property or indexer 'property'* +- [**CS0274**](#accessor-accessibility): *Cannot specify accessibility modifiers for both accessors of the property or indexer 'property'* +- [**CS0275**](#accessor-accessibility): *'accessor': accessibility modifiers may not be used on accessors in an interface* +- [**CS0276**](#accessor-accessibility): *'property': accessibility modifiers on accessors may only be used if the property or indexer has both a get and a set accessor* +- [**CS0442**](#accessor-accessibility): *'property': abstract properties cannot have private accessors* +- [**CS0544**](#property-overrides): *'property': cannot override because 'member' is not a property* +- [**CS0545**](#property-overrides): *'function' : cannot override because 'property' does not have an overridable get accessor* +- [**CS0546**](#property-overrides): *'accessor': cannot override because 'property' does not have an overridable set accessor* +- [**CS0547**](#property-declaration-syntax): *'property': property or indexer cannot have void type* +- [**CS0548**](#property-declaration-syntax): *'property': property or indexer must have at least one accessor* +- [**CS0571**](#property-declaration-syntax): *'function' : cannot explicitly call operator or accessor* +- [**CS0610**](#property-declaration-syntax): *Field or property cannot be of type 'type'* +- [**CS0840**](#property-declaration-syntax): *'Property name' must declare a body because it is not marked abstract or extern. Automatically implemented properties must define both get and set accessors.* +- [**CS1014**](#property-declaration-syntax): *A get or set accessor expected* +- [**CS1043**](#property-declaration-syntax): *{ or ; expected* +- [**CS1715**](#property-overrides): *'type': type must be 'type' to match overridden member 'member'* +- [**CS8050**](#property-initializers): *Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.* +- [**CS8051**](#property-initializers): *Auto-implemented properties must have get accessors.* +- [**CS8053**](#property-initializers): *Instance properties in interfaces cannot have initializers.* +- [**CS8080**](#property-overrides): *Auto-implemented properties must override all accessors of the overridden property.* - [**CS8145**](#ref-returning-properties): *Auto-implemented properties cannot return by reference* - [**CS8147**](#ref-returning-properties): *Properties which return by reference cannot have set accessors* -- [**CS8341**](#readonly-properties): *Auto-implemented instance properties in readonly structs must be readonly* -- [**CS8657**](#readonly-properties): *Static member cannot be marked 'readonly'* -- [**CS8658**](#readonly-properties): *Auto-implemented 'set' accessor cannot be marked 'readonly'* -- [**CS8659**](#readonly-properties): *Auto-implemented property cannot be marked 'readonly' because it has a 'set' accessor* -- [**CS8660**](#readonly-properties): *Cannot specify 'readonly' modifiers on both property and its accessor* -- [**CS8661**](#readonly-properties): *Cannot specify 'readonly' modifiers on both accessors of property* -- [**CS8664**](#readonly-properties): *'readonly' can only be used on accessors if property has both get and set* +- [**CS8341**](#readonly-properties): *Auto-implemented instance properties in readonly structs must be readonly.* +- [**CS8657**](#readonly-properties): *Static member 'member' cannot be marked 'readonly'.* +- [**CS8658**](#readonly-properties): *Auto-implemented 'set' accessor 'accessor' cannot be marked 'readonly'.* +- [**CS8659**](#readonly-properties): *Auto-implemented property 'property' cannot be marked 'readonly' because it has a 'set' accessor.* +- [**CS8660**](#readonly-properties): *Cannot specify 'readonly' modifiers on both property or indexer 'property' and its accessor. Remove one of them.* +- [**CS8661**](#readonly-properties): *Cannot specify 'readonly' modifiers on both accessors of property or indexer 'property'. Instead, put a 'readonly' modifier on the property itself.* +- [**CS8664**](#readonly-properties): *'property': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor* +- [**CS8852**](#init-only-properties): *Init-only property or indexer 'property' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.* +- [**CS8853**](#init-only-properties): *'member' must match by init-only of overridden member 'member'* +- [**CS8855**](#init-only-properties): *Accessors 'accessor' and 'accessor' should both be init-only or neither* +- [**CS8856**](#init-only-properties): *The 'init' accessor is not valid on static members* +- [**CS8903**](#init-only-properties): *'init' accessors cannot be marked 'readonly'. Mark 'property' readonly instead.* - [**CS9029**](#required-members): *Types and aliases cannot be named 'required'.* -- [**CS9030**](#required-members): *Member must be required because it overrides required member.* -- [**CS9031**](#required-members): *Required member cannot be hidden by derived member.* +- [**CS9030**](#required-members): *'member' must be required because it overrides required member 'member'* +- [**CS9031**](#required-members): *Required member 'member' cannot be hidden by 'member'.* - [**CS9032**](#required-members): *Required member cannot be less visible or have a setter less visible than the containing type.* - [**CS9033**](#required-members): *Do not use '`System.Runtime.CompilerServices.RequiredMemberAttribute'`. Use the 'required' keyword on required fields and properties instead.* - [**CS9034**](#required-members): *Required member must be settable.* - [**CS9035**](#required-members): *Required member must be set in the object initializer or attribute constructor.* - [**CS9036**](#required-members): *Required member 'memberName' must be assigned a value, it cannot use a nested member or collection initializer.* -- [**CS9037**](#required-members): *The required members list is malformed and cannot be interpreted.* -- [**CS9038**](#required-members): *The required members list for the base type is malformed and cannot be interpreted. To use this constructor, apply the '`SetsRequiredMembers`' attribute*. +- [**CS9037**](#required-members): *The required members list for 'type' is malformed and cannot be interpreted.* +- [**CS9038**](#required-members): *The required members list for the base type 'type' is malformed and cannot be interpreted. To use this constructor, apply the '`SetsRequiredMembers`' attribute.* - [**CS9039**](#required-members): *This constructor must add '`SetsRequiredMembers`' because it chains to a constructor that has that attribute.* - [**CS9040**](#required-members): *Type cannot satisfy the '`new()`' constraint on parameter in the generic type or method because it has required members.* - [**CS9042**](#required-members): *Required member should not be attributed with '`ObsoleteAttribute`' unless the containing type is obsolete or all constructors are obsolete.* @@ -124,118 +175,153 @@ That's by design. The text closely matches the text of the compiler error / warn The following warnings can be generated for field-backed properties: -- [**CS9264**](#field-backed-properties): *Non-nullable property must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or adding '`[field: MaybeNull, AllowNull]`' attributes.* -- [**CS9266**](#field-backed-properties): *One accessor of property should use '`field`' because the other accessor is using it.* +- [**CS9264**](#field-backed-properties): *Non-nullable property 'property' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or safely handling the case where '`field`' is null in the '`get`' accessor.* +- [**CS9266**](#field-backed-properties): *The 'accessor' accessor of property 'property' should use '`field`' because the other accessor is using it.* - [**CS9273**](#field-backed-properties): *In this language version, '`field`' is a keyword within a property accessor. Rename the variable or use the identifier '`@field`' instead.* The following sections explain the cause and fixes for these errors and warnings. -## Property accessor syntax +## Accessor accessibility -- **CS0545**: *'function' : cannot override because 'property' does not have an overridable get accessor* -- **CS0571**: *'function' : cannot explicitly call operator or accessor* -- **CS1043**: *{ or ; expected* - -To correct property accessor syntax errors, apply one of the following changes based on the specific diagnostic: +- **CS0273**: *The accessibility modifier of the 'accessor' accessor must be more restrictive than the property or indexer 'property'* +- **CS0274**: *Cannot specify accessibility modifiers for both accessors of the property or indexer 'property'* +- **CS0275**: *'accessor': accessibility modifiers may not be used on accessors in an interface* +- **CS0276**: *'property': accessibility modifiers on accessors may only be used if the property or indexer has both a get and a set accessor* +- **CS0442**: *'property': abstract properties cannot have private accessors* -Override only the accessors that exist in the base class property declaration (**CS0545**). You can't override a property accessor that isn't present or accessible in the base class because there's no virtual method to override in the compiled IL. If the base class property has only a `get` accessor, remove the `set` accessor from your override, or add the missing accessor to the base class and mark it `virtual`. Alternatively, use the `new` keyword instead of `override` to hide the base class property with a completely new property definition that has different accessors. +These errors enforce the rules for access modifiers on property and indexer accessors. For the full rules, see [Restricting Accessor Accessibility](../../programming-guide/classes-and-structs/restricting-accessor-accessibility.md) and [Accessors](~/_csharpstandard/standard/classes.md#1573-accessors) in the C# specification. To correct these errors, apply one of the following changes based on the specific diagnostic: -Access properties using property syntax rather than calling accessor methods directly (**CS0571**). Property accessors compile to special methods with names like `get_PropertyName` and `set_PropertyName`, but you should invoke these methods through property syntax (`obj.Property` and `obj.Property = value`). This approach maintains proper semantics and allows the compiler to perform necessary checks. The same principle applies to operators, which compile to methods like `op_Increment` but should be invoked using operator syntax (`++obj`) rather than method calls. +- Use an access modifier that's more restrictive than the property's declared accessibility (**CS0273**). For example, an `internal` property can have a `private` set accessor, but not a `public` one. The accessor's accessibility must be a proper subset of the property's accessibility. +- Apply an access modifier to only one of the two accessors (**CS0274**). The property declaration itself establishes the default accessibility for both accessors, and only one accessor can differ from that default. Place the access modifier on whichever accessor needs restricted access. +- Remove access modifiers from accessors in interface property declarations (**CS0275**). Interface members define a public contract, and access modifiers on individual accessors aren't permitted. If you need to restrict access, apply the restriction in the implementing class instead. +- Add both a `get` and a `set` accessor to the property before applying an access modifier to either one (**CS0276**). Access modifiers on accessors distinguish one accessor's visibility from the other, which requires both accessors to be present. If the property has only one accessor, remove the access modifier from it. +- Change the access modifier on abstract property accessors from `private` to a less restrictive modifier (**CS0442**). Abstract members must be accessible to derived classes so those classes can provide implementations. Use `protected`, `internal`, or `protected internal` instead of `private`. -Use proper property accessor syntax with braces or expression bodies (**CS1043**). Property accessors must follow C# syntax rules: accessor bodies must be enclosed in curly braces `{ }`, expression-bodied accessors must use the `=>` syntax, and auto-implemented properties must end with a semicolon after the accessor list. The compiler expects either a complete accessor implementation or the semicolon that indicates an auto-implemented accessor. +For more information, see [Restricting Accessor Accessibility](../../programming-guide/classes-and-structs/restricting-accessor-accessibility.md) and [Properties](../../programming-guide/classes-and-structs/properties.md). -For more information, see [Properties](../../programming-guide/classes-and-structs/properties.md), [Inheritance](../../fundamentals/object-oriented/inheritance.md), and [Using Properties](../../programming-guide/classes-and-structs/using-properties.md). - -## Auto-implemented properties +## Property declaration syntax +- **CS0547**: *'property': property or indexer cannot have void type* +- **CS0548**: *'property': property or indexer must have at least one accessor* +- **CS0571**: *'function' : cannot explicitly call operator or accessor* +- **CS0610**: *Field or property cannot be of type 'type'* - **CS0840**: *'Property name' must declare a body because it is not marked abstract or extern. Automatically implemented properties must define both get and set accessors.* - **CS1014**: *A get or set accessor expected* +- **CS1043**: *{ or ; expected* -To correct auto-implemented property errors, apply one of the following changes based on the specific diagnostic: +These errors enforce the structural requirements of property and indexer declarations. For the full rules, see [Properties](../../programming-guide/classes-and-structs/properties.md) and the [Properties](~/_csharpstandard/standard/classes.md#157-properties) section of the C# specification. To correct these errors, apply one of the following changes based on the specific diagnostic: -Add both `get` and `set` accessors to the property declaration (**CS0840**). Auto-implemented properties require the compiler to generate a backing field, and the compiler can only do this when both accessors are present to ensure the storage can be both read and written. If you need a read-only auto-implemented property, include a `set` accessor and make it `private` to restrict write access while still allowing the compiler to generate the backing field. Alternatively, if the property is declared as `abstract` or `extern`, remove the accessor bodies entirely since these modifiers indicate the implementation is provided elsewhere. For `partial` properties, you can split the declaration and implementation across partial type declarations. +- Change the property type from `void` to a valid type (**CS0547**). Properties and indexers represent expressions, and [`void`](../builtin-types/void.md) isn't a valid expression type. Choose the appropriate type for the data the property represents. +- Add at least one accessor (`get`, `set`, or `init`) to the property declaration (**CS0548**). A property without any accessors has no way to read or write its value. Include a `get` accessor, a `set` or `init` accessor, or both. +- Access properties using property syntax rather than calling accessor methods directly (**CS0571**). Property accessors compile to methods named `get_PropertyName` and `set_PropertyName`, but invoke these methods through property syntax (`obj.Property` and `obj.Property = value`). The same principle applies to operators. Use operator syntax (`++obj`) rather than calling methods like `op_Increment`. +- Change the field or property type from a restricted type to an allowed type (**CS0610**). Certain types like and can't be used as fields or properties. These types can still be used as method parameters or local variables. +- For a read-only property, declare a `get`-only auto-implemented property. Otherwise, add both `get` and `set` accessors to auto-implemented property declarations, or provide explicit accessor bodies (**CS0840**). For a property that needs custom logic, use the `field` keyword, added in C# 13 to access the compiler-synthesized backing field in your accessor body. For `abstract` or `extern` properties, remove the accessor bodies because the implementation is provided elsewhere. For `partial` properties, split the declaration and implementation across partial type declarations. +- Ensure the property body contains only valid accessor keywords—`get`, `set`, or `init` (**CS1014**). Property bodies can't contain arbitrary statements or member declarations. Move fields and methods outside the property to the class or struct body. +- Use proper property accessor syntax with braces or expression bodies (**CS1043**). Accessor bodies must use curly braces `{ }`, expression-bodied accessors must use `=>` syntax, and auto-implemented properties must end with a semicolon after the accessor token. -Ensure the property declaration contains only valid accessor keywords `get` and `set` (**CS1014**). Property syntax only allows accessor declarations, not arbitrary statements or member declarations within the property body. If you need additional logic, implement the property with explicit accessor bodies that contain your code. If you're attempting to declare fields or methods, move those declarations outside the property to the class or struct body where member declarations are allowed. +For more information, see [Properties](../../programming-guide/classes-and-structs/properties.md), [Auto-Implemented Properties](../../programming-guide/classes-and-structs/auto-implemented-properties.md), and [Using Properties](../../programming-guide/classes-and-structs/using-properties.md). -For more information, see [Properties](../../programming-guide/classes-and-structs/properties.md) and [Auto-Implemented Properties](../../programming-guide/classes-and-structs/auto-implemented-properties.md). +## Property overrides + +- **CS0544**: *'property': cannot override because 'member' is not a property* +- **CS0545**: *'function' : cannot override because 'property' does not have an overridable get accessor* +- **CS0546**: *'accessor': cannot override because 'property' does not have an overridable set accessor* +- **CS1715**: *'type': type must be 'type' to match overridden member 'member'* +- **CS8080**: *Auto-implemented properties must override all accessors of the overridden property.* + +These errors enforce the rules for overriding properties in derived classes. For the full rules, see [Inheritance](../../fundamentals/object-oriented/inheritance.md) and the [Virtual, sealed, override, and abstract accessors](~/_csharpstandard/standard/classes.md#1576-virtual-sealed-override-and-abstract-accessors) section of the C# specification. To correct these errors, apply one of the following changes based on the specific diagnostic: + +- Ensure the member you're overriding is a property, not a field or method (**CS0544**). The `override` keyword on a property can only target a `virtual`, `abstract`, or `override` property in a base class. To shadow a non-property member with a property, use the `new` keyword instead of `override`. +- Override only the accessors that exist in the base class property declaration (**CS0545**, **CS0546**). You can't override a property accessor that isn't present or accessible in the base class because there's no virtual method to override. If the base class property has only a `get` accessor, you can't add a `set` accessor through an override. Either add the missing accessor to the base class and mark it `virtual`, or use `new` to hide the base class property with a new property definition. +- Match the type of the overriding property to the type of the overridden member (**CS1715**). Read-only (`get`-only) properties support covariant return types beginning with C# 9, so the override can return a more derived type. However, properties that have a `set` or `init` accessor require an exact type match because the setter accepts values of the declared type. If you need a different type on a property with a setter, change the property type in the derived class to match the base class declaration, or remove the setter and use a covariant return type instead. +- Include all accessors from the base property when overriding with an auto-implemented property (**CS8080**). Auto-implemented properties generate both storage and accessor implementations, so they must override all accessors present in the base class. If the base property has both `get` and `set`, the auto-implemented override must also have both. To override only specific accessors, implement the property with explicit accessor bodies and an explicit backing field instead of using auto-implementation. + +For more information, see [Inheritance](../../fundamentals/object-oriented/inheritance.md), [Properties](../../programming-guide/classes-and-structs/properties.md), and [Using Properties](../../programming-guide/classes-and-structs/using-properties.md). ## Field-backed properties - **CS9258**: *In this language version, the '`field`' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use '`this.field`' or '`@field`' instead.* - **CS9263**: *A partial property cannot have an initializer on both the definition and implementation.* -- **CS9264**: *Non-nullable property must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or adding '`[field: MaybeNull, AllowNull]`' attributes.* -- **CS9266**: *One accessor of property should use '`field`' because the other accessor is using it.* +- **CS9264**: *Non-nullable property 'property' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or safely handling the case where '`field`' is null in the '`get`' accessor.* +- **CS9266**: *The 'accessor' accessor of property 'property' should use '`field`' because the other accessor is using it.* - **CS9273**: *In this language version, '`field`' is a keyword within a property accessor. Rename the variable or use the identifier '`@field`' instead.* To correct field-backed property errors, apply one of the following changes based on the specific diagnostic: -Rename any variable named `field` to a different identifier, or use the `@field` escape syntax to refer to your variable (**CS9258**, **CS9273**). This correction is necessary because `field` is a contextual keyword within property accessors in C# 13 and later, where it refers to the compiler-synthesized backing field. If you want to access an existing member named `field` instead of the synthesized backing field, qualify it with `this.field` to disambiguate the reference. - -Remove the initializer from either the partial property definition or the implementation, keeping only one (**CS9263**). This correction is required because allowing initializers in both locations would create ambiguity about which value should be used and could lead to the backing field being initialized twice with potentially different values. - -Add the `[field: MaybeNull, AllowNull]` attributes to the property declaration to indicate that the backing field should be treated as nullable (**CS9264**). This correction aligns the nullability expectations between the property type and the compiler-synthesized backing field, resolving the mismatch where the property is declared as non-nullable but the `field` keyword usage suggests it could be null. Alternatively, change the property type to nullable, add the `required` modifier to ensure initialization, or initialize the property in the constructor. - -Consistently use either the `field` keyword in both accessors or use an explicit backing field in both accessors (**CS9266**). This correction prevents potential bugs where one accessor modifies the compiler-synthesized backing field while the other accessor modifies a different storage location, leading to inconsistent property behavior. +- Rename any variable named `field` to a different identifier, or use the `@field` escape syntax to refer to your variable (**CS9258**, **CS9273**). This correction is necessary because `field` is a contextual keyword within property accessors in C# 13 and later, where it refers to the compiler-synthesized backing field. If you want to access an existing member named `field` instead of the synthesized backing field, qualify it with `this.field` to disambiguate the reference. +- Remove the initializer from either the partial property definition or the implementation, keeping only one (**CS9263**). This correction is required because allowing initializers in both locations would create ambiguity about which value should be used and could lead to the backing field being initialized twice with potentially different values. +- Add the `[field: MaybeNull, AllowNull]` attributes to the property declaration to indicate that the backing field should be treated as nullable (**CS9264**). This correction aligns the nullability expectations between the property type and the compiler-synthesized backing field, resolving the mismatch where the property is declared as non-nullable but the `field` keyword usage suggests it could be null. Alternatively, change the property type to nullable, add the `required` modifier to ensure initialization, or initialize the property in the constructor. +- Consistently use either the `field` keyword in both accessors or use an explicit backing field in both accessors (**CS9266**). This correction prevents potential bugs where one accessor modifies the compiler-synthesized backing field while the other accessor modifies a different storage location, leading to inconsistent property behavior. For more information, see [field keyword](../../programming-guide/classes-and-structs/properties.md#field-backed-properties) and [Partial properties](../../programming-guide/classes-and-structs/partial-classes-and-methods.md#partial-members). ## Readonly properties - **CS0200**: *Property or indexer 'property' cannot be assigned to -- it is read only* -- **CS8341**: *Auto-implemented instance properties in readonly structs must be readonly* -- **CS8657**: *Static member cannot be marked 'readonly'* -- **CS8658**: *Auto-implemented 'set' accessor cannot be marked 'readonly'* -- **CS8659**: *Auto-implemented property cannot be marked 'readonly' because it has a 'set' accessor* -- **CS8660**: *Cannot specify 'readonly' modifiers on both property and its accessor* -- **CS8661**: *Cannot specify 'readonly' modifiers on both accessors of property* -- **CS8664**: *'readonly' can only be used on accessors if property has both get and set* +- **CS8341**: *Auto-implemented instance properties in readonly structs must be readonly.* +- **CS8657**: *Static member 'member' cannot be marked 'readonly'.* +- **CS8658**: *Auto-implemented 'set' accessor 'accessor' cannot be marked 'readonly'.* +- **CS8659**: *Auto-implemented property 'property' cannot be marked 'readonly' because it has a 'set' accessor.* +- **CS8660**: *Cannot specify 'readonly' modifiers on both property or indexer 'property' and its accessor. Remove one of them.* +- **CS8661**: *Cannot specify 'readonly' modifiers on both accessors of property or indexer 'property'. Instead, put a 'readonly' modifier on the property itself.* +- **CS8664**: *'property': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor* To correct readonly property errors, apply one of the following changes based on the specific diagnostic: -Add a `set` or `init` accessor to the property to make it writable (**CS0200**). This correction is necessary because properties without set accessors are read-only and can only be assigned in the declaring type's constructor or field initializer. If you need the property to be set during object initialization but immutable afterward, use an `init` accessor instead of a `set` accessor. If the property should remain read-only, move the assignment into the constructor where initialization is permitted, or reconsider whether the assignment is necessary at all. - -Mark auto-implemented instance properties as `readonly` when you declare them within a `readonly struct` (**CS8341**). This correction enforces the immutability contract of the containing struct, ensuring that all instance members respect the `readonly` guarantee. If the property needs to be mutable, either remove the `readonly` modifier from the struct declaration or implement the property with an explicit backing field and accessor bodies that don't modify instance state. +- Add a `set` or `init` accessor to the property to make it writable (**CS0200**). This correction is necessary because properties without set accessors are read-only and can only be assigned in the declaring type's constructor or field initializer. If you need the property to be set during object initialization but immutable afterward, use an `init` accessor instead of a `set` accessor. If the property should remain read-only, move the assignment into the constructor where initialization is permitted, or reconsider whether the assignment is necessary at all. +- Mark auto-implemented instance properties as `readonly` when you declare them within a `readonly struct` (**CS8341**). This correction enforces the immutability contract of the containing struct, ensuring that all instance members respect the `readonly` guarantee. If the property needs to be mutable, either remove the `readonly` modifier from the struct declaration or implement the property with an explicit backing field and accessor bodies that don't modify instance state. +- Remove the `readonly` modifier from static property or accessor declarations (**CS8657**). This correction is required because the `readonly` modifier only applies to instance members of structs to indicate they don't modify instance state, and static members don't have instance state to protect. If you need a read-only static property, simply omit the `set` accessor rather than using the `readonly` modifier. +- Remove the `readonly` modifier from auto-implemented `set` accessors, or apply it to the `get` accessor only (**CS8658**). This correction is necessary because `set` accessors inherently modify state, which contradicts the purpose of the `readonly` modifier that guarantees no modification of instance state. If you need a property that can be set during initialization but is read-only afterward, use an `init` accessor instead of a `set` accessor. +- Remove the `readonly` modifier from the property declaration when the property has a `set` accessor (**CS8659**). This correction is required because properties with `set` accessors can modify instance state, which violates the `readonly` guarantee. If you need initialization-time setting only, replace the `set` accessor with an `init` accessor, or remove the `set` accessor entirely to make the property truly read-only. +- Place the `readonly` modifier either on the property declaration or on individual accessors, but not both (**CS8660**, **CS8661**). This correction prevents redundant modifier declarations that could create confusion about which modifier takes precedence. If you want to mark specific accessors as `readonly`, remove the modifier from the property declaration and place it only on the accessors. Alternatively, if all accessors should be `readonly`, mark the property itself rather than individual accessors. +- Ensure both `get` and `set` accessors are present when marking individual accessors as `readonly` (**CS8664**). This correction is necessary because the `readonly` modifier on individual accessors distinguishes between accessors that modify state and those that don't, which only makes sense when both accessor types exist. If the property has only a `get` accessor, mark the entire property as `readonly` instead of the individual accessor. -Remove the `readonly` modifier from static property or accessor declarations (**CS8657**). This correction is required because the `readonly` modifier only applies to instance members of structs to indicate they don't modify instance state, and static members don't have instance state to protect. If you need a read-only static property, simply omit the `set` accessor rather than using the `readonly` modifier. +For more information, see [readonly instance members](../builtin-types/struct.md#readonly-instance-members), [init keyword](../keywords/init.md), and [Properties](../../programming-guide/classes-and-structs/properties.md). -Remove the `readonly` modifier from auto-implemented `set` accessors, or apply it to the `get` accessor only (**CS8658**). This correction is necessary because `set` accessors inherently modify state, which contradicts the purpose of the `readonly` modifier that guarantees no modification of instance state. If you need a property that can be set during initialization but is read-only afterward, use an `init` accessor instead of a `set` accessor. +## Init-only properties -Remove the `readonly` modifier from the property declaration when the property has a `set` accessor (**CS8659**). This correction is required because properties with `set` accessors can modify instance state, which violates the `readonly` guarantee. If you need initialization-time setting only, replace the `set` accessor with an `init` accessor, or remove the `set` accessor entirely to make the property truly read-only. +- **CS8852**: *Init-only property or indexer 'property' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.* +- **CS8853**: *'member' must match by init-only of overridden member 'member'* +- **CS8855**: *Accessors 'accessor' and 'accessor' should both be init-only or neither* +- **CS8856**: *The 'init' accessor is not valid on static members* +- **CS8903**: *'init' accessors cannot be marked 'readonly'. Mark 'property' readonly instead.* -Place the `readonly` modifier either on the property declaration or on individual accessors, but not both (**CS8660**, **CS8661**). This correction prevents redundant modifier declarations that could create confusion about which modifier takes precedence. If you want to mark specific accessors as `readonly`, remove the modifier from the property declaration and place it only on the accessors. Alternatively, if all accessors should be `readonly`, mark the property itself rather than individual accessors. +These errors enforce the rules for the `init` accessor, which enables immutable object initialization. For the full rules, see [init keyword](../keywords/init.md). To correct these errors, apply one of the following changes based on the specific diagnostic: -Ensure both `get` and `set` accessors are present when marking individual accessors as `readonly` (**CS8664**). This correction is necessary because the `readonly` modifier on individual accessors distinguishes between accessors that modify state and those that don't, which only makes sense when both accessor types exist. If the property has only a `get` accessor, mark the entire property as `readonly` instead of the individual accessor. +- Move assignments to init-only properties into an object initializer, a constructor, or an `init` accessor (**CS8852**). Init-only properties can't be assigned after object construction completes. Assign the value in the constructor body, an `init` accessor, or an object initializer expression (`new MyType { Property = value }`). If you need to assign the property after construction, change the `init` accessor to a `set` accessor. +- Match the `init` or `set` accessor kind when overriding a property (**CS8853**). If the base class property uses an `init` accessor, the overriding property must also use `init`. Similarly, if the base uses `set`, the override must use `set`. This consistency ensures that the immutability contract established by the base type is preserved in derived types. +- Use the same accessor kind (`init` or `set`) on both accessors in an explicit interface implementation (**CS8855**). When a type explicitly implements two interfaces that declare the same property, both accessor implementations must agree: either both use `init` or both use `set`. +- Remove the `init` accessor from static property declarations, or change it to a `set` accessor (**CS8856**). The `init` accessor is designed for instance initialization patterns tied to object construction, and static members don't participate in object initialization. Use a `set` accessor for mutable static properties, or remove the setter entirely for read-only static properties. +- Remove the `readonly` modifier from the `init` accessor (**CS8903**). The `readonly` modifier on a struct member guarantees that the member doesn't mutate the struct instance. Because an `init` accessor assigns to instance state, it inherently mutates the struct and can't be marked `readonly`. To make the `get` accessor `readonly`, apply the `readonly` modifier to the `get` accessor only. -For more information, see [readonly instance members](../builtin-types/struct.md#readonly-instance-members), [init keyword](../keywords/init.md), and [Properties](../../programming-guide/classes-and-structs/properties.md). +For more information, see [init keyword](../keywords/init.md) and [Object and Collection Initializers](../../programming-guide/classes-and-structs/object-and-collection-initializers.md). ## Property initializers -- **CS8050**: *Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers* -- **CS8051**: *Auto-implemented properties must have get accessors* -- **CS8053**: *Instance properties in interfaces cannot have initializers* +- **CS8050**: *Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.* +- **CS8051**: *Auto-implemented properties must have get accessors.* +- **CS8053**: *Instance properties in interfaces cannot have initializers.* To correct property initializer errors, apply one of the following changes based on the specific diagnostic: -Convert the property to use auto-implemented syntax by removing the accessor bodies and letting the compiler generate the backing field (**CS8050**). This correction is needed because only properties with compiler-managed storage can have initializers, ensuring the initialization occurs before any accessor logic executes. Alternatively, modify the accessor implementations to use the `field` keyword to access the compiler-synthesized backing field. This approach enables the initializer while maintaining custom accessor logic. If neither approach is suitable, remove the initializer and assign the value in a constructor instead, where you have full control over the initialization sequence. - -Add a `get` accessor to the auto-implemented property to enable reading the initialized value (**CS8051**). This correction is required because initializers set a value that must be retrievable, and a write-only property violates this fundamental expectation of property initialization. If you truly need a write-only property, implement the accessors explicitly with a backing field and assign the field directly in a constructor rather than using a property initializer. - -Remove the initializer from interface property declarations (**CS8053**). This correction is necessary because interfaces define contracts for implementing types rather than providing concrete implementations with initial values. If you need to provide default values, implement the property in a class that implements the interface, or use default interface methods (available in C# 8.0 and later) to supply a default implementation. +- Convert the property to use auto-implemented syntax by removing the accessor bodies and letting the compiler generate the backing field (**CS8050**). This correction is needed because only properties with compiler-managed storage can have initializers, ensuring the initialization occurs before any accessor logic executes. Alternatively, modify the accessor implementations to use the `field` keyword to access the compiler-synthesized backing field. This approach enables the initializer while maintaining custom accessor logic. If neither approach is suitable, remove the initializer and assign the value in a constructor instead, where you have full control over the initialization sequence. +- Add a `get` accessor to the auto-implemented property to enable reading the initialized value (**CS8051**). This correction is required because initializers set a value that must be retrievable, and a write-only property violates this fundamental expectation of property initialization. If you truly need a write-only property, implement the accessors explicitly with a backing field and assign the field directly in a constructor rather than using a property initializer. +- Remove the initializer from interface property declarations (**CS8053**). This correction is necessary because interfaces define contracts for implementing types rather than providing concrete implementations with initial values. If you need to provide default values, implement the property in a class that implements the interface, or use default interface methods (available in C# 8.0 and later) to supply a default implementation. For more information, see [Properties](../../programming-guide/classes-and-structs/properties.md), [Auto-Implemented Properties](../../programming-guide/classes-and-structs/auto-implemented-properties.md), and [field keyword](../../programming-guide/classes-and-structs/properties.md#field-backed-properties). ## Required members - **CS9029**: *Types and aliases cannot be named 'required'.* -- **CS9030**: *Member must be required because it overrides required member.* -- **CS9031**: *Required member cannot be hidden by derived member.* +- **CS9030**: *'member' must be required because it overrides required member 'member'* +- **CS9031**: *Required member 'member' cannot be hidden by 'member'.* - **CS9032**: *Required member cannot be less visible or have a setter less visible than the containing type.* - **CS9033**: *Do not use '`System.Runtime.CompilerServices.RequiredMemberAttribute'`. Use the 'required' keyword on required fields and properties instead.* - **CS9034**: *Required member must be settable.* - **CS9035**: *Required member must be set in the object initializer or attribute constructor.* - **CS9036**: *Required member 'memberName' must be assigned a value, it cannot use a nested member or collection initializer.* -- **CS9037**: *The required members list is malformed and cannot be interpreted.* -- **CS9038**: *The required members list for the base type is malformed and cannot be interpreted. To use this constructor, apply the '`SetsRequiredMembers`' attribute*. +- **CS9037**: *The required members list for 'type' is malformed and cannot be interpreted.* +- **CS9038**: *The required members list for the base type 'type' is malformed and cannot be interpreted. To use this constructor, apply the '`SetsRequiredMembers`' attribute.* - **CS9039**: *This constructor must add '`SetsRequiredMembers`' because it chains to a constructor that has that attribute.* - **CS9040**: *Type cannot satisfy the '`new()`' constraint on parameter in the generic type or method because it has required members.* - **CS9042**: *Required member should not be attributed with '`ObsoleteAttribute`' unless the containing type is obsolete or all constructors are obsolete.* @@ -243,19 +329,13 @@ For more information, see [Properties](../../programming-guide/classes-and-struc To correct required member errors, apply one of the following changes based on the specific diagnostic: -Avoid using `required` as a type or alias name (**CS9029**). This correction is necessary because `required` is a contextual keyword in C# 11 and later, and using it as a type name creates ambiguity in code where the keyword might appear. - -Ensure derived members maintain the `required` modifier when overriding required members (**CS9030**). This correction enforces the contract established by the base class, guaranteeing that all derived types maintain the same initialization requirements. Avoid hiding required members with non-required members in derived classes (**CS9031**), as this action breaks the initialization contract that consumers expect from the base type. - -Make required members at least as visible as their containing type, and ensure property setters are also sufficiently visible (**CS9032**). This correction prevents situations where a type is publicly accessible but its required members can't be initialized from all contexts where the type is constructed. - -Use the `required` keyword instead of manually applying `RequiredMemberAttribute` (**CS9033**). This correction ensures the compiler generates the correct metadata and enforces all required member rules, which manual attribute application might not do correctly. - -Ensure required members have set accessors or are otherwise settable (**CS9034**). This correction is necessary because required members must be initialized during object creation, which requires write access. When creating instances, initialize required members directly in object initializers (**CS9035**, **CS9036**). You must assign a value to each required member rather than using nested member initializers or collection initializers, because the required member itself must be set before accessing its properties. - -Apply the `SetsRequiredMembers` attribute to constructors that initialize all required members in their bodies (**CS9038**, **CS9039**). This correction informs the compiler that the constructor fulfills the required member contract, allowing object creation without object initializers. If a constructor chains to another constructor with `SetsRequiredMembers`, it must also have the attribute. - -Avoid using required members in types that must satisfy the `new()` constraint (**CS9040**), as the parameterless constructor can't guarantee required member initialization without an object initializer. Don't mark required members as obsolete unless the containing type or all constructors are obsolete (**CS9042**), to prevent situations where members are required but their use is discouraged. Required members aren't allowed in top-level statements or script contexts (**CS9045**) because these contexts don't support the object initialization syntax needed to set required members. +- Avoid using `required` as a type or alias name (**CS9029**). This correction is necessary because `required` is a contextual keyword in C# 11 and later, and using it as a type name creates ambiguity in code where the keyword might appear. +- Ensure derived members maintain the `required` modifier when overriding required members (**CS9030**). This correction enforces the contract established by the base class, guaranteeing that all derived types maintain the same initialization requirements. Avoid hiding required members with non-required members in derived classes (**CS9031**), as this action breaks the initialization contract that consumers expect from the base type. +- Make required members at least as visible as their containing type, and ensure property setters are also sufficiently visible (**CS9032**). This correction prevents situations where a type is publicly accessible but its required members can't be initialized from all contexts where the type is constructed. +- Use the `required` keyword instead of manually applying (**CS9033**). This correction ensures the compiler generates the correct metadata and enforces all required member rules, which manual attribute application might not do correctly. +- Ensure required members have set accessors or are otherwise settable (**CS9034**). This correction is necessary because required members must be initialized during object creation, which requires write access. When creating instances, initialize required members directly in object initializers (**CS9035**, **CS9036**). You must assign a value to each required member rather than using nested member initializers or collection initializers, because the required member itself must be set before accessing its properties. +- Apply the `SetsRequiredMembers` attribute to constructors that initialize all required members in their bodies (**CS9038**, **CS9039**). This correction informs the compiler that the constructor fulfills the required member contract, allowing object creation without object initializers. If a constructor chains to another constructor with `SetsRequiredMembers`, it must also have the attribute. +- Avoid using required members in types that must satisfy the `new()` constraint (**CS9040**), as the parameterless constructor can't guarantee required member initialization without an object initializer. Don't mark required members as obsolete unless the containing type or all constructors are obsolete (**CS9042**), to prevent situations where members are required but their use is discouraged. Required members aren't allowed in top-level statements or script contexts (**CS9045**) because these contexts don't support the object initialization syntax needed to set required members. For more information, see the [required modifier](../keywords/required.md) reference article and [Object and Collection Initializers](../../programming-guide/classes-and-structs/object-and-collection-initializers.md) guide. @@ -266,8 +346,7 @@ For more information, see the [required modifier](../keywords/required.md) refer To correct ref-returning property errors, apply one of the following changes based on the specific diagnostic: -Implement the property explicitly with a backing field and use the `ref` keyword in the `get` accessor's return expression (**CS8145**). This correction is necessary because auto-implemented properties generate a private backing field that the compiler manages internally. Returning a reference to a private field would expose internal storage that callers shouldn't access directly. To create a ref-returning property, declare an explicit field and return it with `=> ref backingField` syntax. Alternatively, if you don't need to return a reference to allow direct modification of the storage, remove the `ref` modifier from the property declaration. - -Remove the `set` accessor from ref-returning properties (**CS8147**). This correction is required because a ref-returning property already provides both read and write access through the returned reference itself. Callers can directly modify the value through the reference without needing a separate setter method. Including a `set` accessor would create two different mechanisms for modifying the same storage, which is redundant and could lead to confusion about which modification path should be used. +- Implement the property explicitly with a backing field and use the `ref` keyword in the `get` accessor's return expression (**CS8145**). This correction is necessary because auto-implemented properties generate a private backing field that the compiler manages internally. Returning a reference to a private field would expose internal storage that callers shouldn't access directly. To create a ref-returning property, declare an explicit field and return it with `=> ref backingField` syntax. Alternatively, if you don't need to return a reference to allow direct modification of the storage, remove the `ref` modifier from the property declaration. +- Remove the `set` accessor from ref-returning properties (**CS8147**). This correction is required because a ref-returning property already provides both read and write access through the returned reference itself. Callers can directly modify the value through the reference without needing a separate setter method. Including a `set` accessor would create two different mechanisms for modifying the same storage, which is redundant and could lead to confusion about which modification path should be used. For more information, see [ref returns and ref locals](../statements/jump-statements.md#ref-returns) and [Properties](../../programming-guide/classes-and-structs/properties.md). diff --git a/docs/csharp/language-reference/compiler-messages/record-declaration-errors.md b/docs/csharp/language-reference/compiler-messages/record-declaration-errors.md index e17705f656691..ea70a53eda165 100644 --- a/docs/csharp/language-reference/compiler-messages/record-declaration-errors.md +++ b/docs/csharp/language-reference/compiler-messages/record-declaration-errors.md @@ -3,41 +3,51 @@ title: Resolve errors and warnings related to record declarations description: Learn how to diagnose and correct C# compiler errors and warnings when you declare record types, either record struct types or record class types. f1_keywords: - "CS8851" + - "CS8857" - "CS8858" - "CS8859" - "CS8860" - "CS8864" - "CS8865" - "CS8866" + - "CS8869" - "CS8870" + - "CS8871" - "CS8872" - "CS8873" - "CS8874" - "CS8875" + - "CS8876" - "CS8877" - "CS8879" + - "CS8906" - "CS8907" - "CS8908" - "CS8913" helpviewer_keywords: - "CS8851" + - "CS8857" - "CS8858" - "CS8859" - "CS8860" - "CS8864" - "CS8865" - "CS8866" + - "CS8869" - "CS8870" + - "CS8871" - "CS8872" - "CS8873" - "CS8874" - "CS8875" + - "CS8876" - "CS8877" - "CS8879" + - "CS8906" - "CS8907" - "CS8908" - "CS8913" -ms.date: 03/06/2026 +ms.date: 04/29/2026 ai-usage: ai-assisted --- # Resolve errors and warnings for record declarations @@ -47,76 +57,90 @@ The C# compiler generates errors and warnings when you misuse [record types](../ -- [**CS8851**](#equality-members): *'Type' defines 'Equals' but not 'GetHashCode'* -- [**CS8858**](#equality-members): *The receiver type is not a valid record type and is not a struct type.* +- [**CS8851**](#equality-members): *'type' defines 'Equals' but not 'GetHashCode'* +- [**CS8857**](#equality-members): *The receiver of a `with` expression must have a non-void type.* +- [**CS8858**](#equality-members): *The receiver type 'type' is not a valid record type and is not a struct type.* - [**CS8859**](#reserved-member-names): *Members named 'Clone' are disallowed in records.* - [**CS8860**](#reserved-member-names): *Types and aliases should not be named 'record'.* - [**CS8864**](#record-inheritance): *Records may only inherit from object or another record.* - [**CS8865**](#record-inheritance): *Only records may inherit from records.* -- [**CS8866**](#positional-members): *Record member must be a readable instance property or field of the type to match the positional parameter.* -- [**CS8870**](#synthesized-member-signatures): *Member cannot be sealed because containing record is not sealed.* -- [**CS8872**](#synthesized-member-signatures): *Member must allow overriding because the containing record is not sealed.* -- [**CS8873**](#synthesized-member-signatures): *Record member must be public.* -- [**CS8874**](#synthesized-member-signatures): *Record member must return type.* -- [**CS8875**](#synthesized-member-signatures): *Record member must be protected.* -- [**CS8877**](#synthesized-member-signatures): *Record member may not be static.* -- [**CS8879**](#synthesized-member-signatures): *Record member must be private.* -- [**CS8908**](#positional-members): *The type may not be used for a field of a record.* -- [**CS8913**](#positional-members): *The positional member found corresponding to this parameter is hidden.* +- [**CS8866**](#positional-members): *Record member 'member' must be a readable instance property or field of type 'type' to match positional parameter 'parameter'.* +- [**CS8869**](#synthesized-member-signatures): *'member' does not override expected method from 'object'.* +- [**CS8870**](#synthesized-member-signatures): *'member' cannot be sealed because containing record is not sealed.* +- [**CS8871**](#synthesized-member-signatures): *'member' does not override expected method from 'type'.* +- [**CS8872**](#synthesized-member-signatures): *'member' must allow overriding because the containing record is not sealed.* +- [**CS8873**](#synthesized-member-signatures): *Record member 'member' must be public.* +- [**CS8874**](#synthesized-member-signatures): *Record member 'member' must return 'type'.* +- [**CS8875**](#synthesized-member-signatures): *Record member 'member' must be protected.* +- [**CS8876**](#synthesized-member-signatures): *'member' does not override expected property from 'type'.* +- [**CS8877**](#synthesized-member-signatures): *Record member 'member' may not be static.* +- [**CS8879**](#synthesized-member-signatures): *Record member 'member' must be private.* +- [**CS8906**](#synthesized-member-signatures): *Record equality contract property 'member' must have a get accessor.* +- [**CS8908**](#positional-members): *The type 'type' may not be used for a field of a record.* +- [**CS8913**](#positional-members): *The positional member 'member' found corresponding to this parameter is hidden.* In addition, this article covers the following warning: -- [**CS8907**](#positional-members): *Parameter is unread. Did you forget to use it to initialize the property with that name?* +- [**CS8907**](#positional-members): *Parameter 'name' is unread. Did you forget to use it to initialize the property with that name?* ## Synthesized member signatures -- **CS8870**: *Member cannot be sealed because containing record is not sealed.* -- **CS8872**: *Member must allow overriding because the containing record is not sealed.* -- **CS8873**: *Record member must be public.* -- **CS8874**: *Record member must return type.* -- **CS8875**: *Record member must be protected.* -- **CS8877**: *Record member may not be static.* -- **CS8879**: *Record member must be private.* +- **CS8869**: *'member' does not override expected method from 'object'.* +- **CS8870**: *'member' cannot be sealed because containing record is not sealed.* +- **CS8871**: *'member' does not override expected method from 'type'.* +- **CS8872**: *'member' must allow overriding because the containing record is not sealed.* +- **CS8873**: *Record member 'member' must be public.* +- **CS8874**: *Record member 'member' must return 'type'.* +- **CS8875**: *Record member 'member' must be protected.* +- **CS8876**: *'member' does not override expected property from 'type'.* +- **CS8877**: *Record member 'member' may not be static.* +- **CS8879**: *Record member 'member' must be private.* +- **CS8906**: *Record equality contract property 'member' must have a get accessor.* When you explicitly declare a member that the compiler would otherwise synthesize for a [record type](../builtin-types/record.md), your declaration must match the expected signature, accessibility, and modifiers. For the complete rules, see the [records specification](~/_csharpstandard/standard/classes.md#1516-synthesized-record-class-members) in the C# language specification. To correct these errors, apply the following changes to your explicitly declared record members: - Change the accessibility of the `Equals` method, `GetHashCode`, `ToString`, the deconstruct method, and the `EqualityContract` property or `op_Equality` and `op_Inequality` operators to `public`. The compiler requires these members to be publicly accessible so that value-based [equality](../builtin-types/record.md#value-equality) works correctly across all calling contexts (**CS8873**). -1-- Change the accessibility of the `PrintMembers` method to `protected` when you declare it in a non-sealed record class. The method must be `protected` because derived records override it to include their own properties in the [formatted output](../builtin-types/record.md#built-in-formatting-for-display) (**CS8875**). +- Change the accessibility of the `PrintMembers` method to `protected` when you declare it in a non-sealed record class. The method must be `protected` because derived records override it to include their own properties in the [formatted output](../builtin-types/record.md#built-in-formatting-for-display) (**CS8875**). - Change the accessibility of the `PrintMembers` method to `private` when you declare it in a sealed record class or a record struct. Because no derived type can exist, the method doesn't need to be accessible outside the type (**CS8879**). - Remove the `static` modifier from any explicitly declared synthesized member. Synthesized members operate on specific record instances to implement behaviors like equality comparison, formatting, and copying, so they must be instance members (**CS8877**). - Change the return type of the member to match the type the compiler expects. For example, the `EqualityContract` property must return `System.Type`, and the `Equals` method must return `bool`. The compiler relies on these exact return types to generate correct code for [equality](../builtin-types/record.md#equality-in-inheritance-hierarchies) and other synthesized behaviors (**CS8874**). - Remove the `sealed` modifier from any explicitly declared synthesized member in a non-sealed record. Derived records must be able to override these members to provide their own value-based equality and formatting logic (**CS8870**). - Declare explicitly provided synthesized members as `virtual` or `override` in a non-sealed record. The compiler requires these members to allow overriding so that derived records in the [inheritance hierarchy](../builtin-types/record.md#equality-in-inheritance-hierarchies) can customize their behavior (**CS8872**). +- Ensure that your explicitly declared member overrides the expected method from `object` or from the base record type. For example, the `Equals` method must override `object.Equals`, and `GetHashCode` must override `object.GetHashCode`. The compiler checks that these members participate in the correct override chain so that [value-based equality](../builtin-types/record.md#value-equality) and other synthesized behaviors work correctly across the type hierarchy (**CS8869**, **CS8871**). +- Ensure that your explicitly declared `EqualityContract` property overrides the base record's `EqualityContract` property. The compiler relies on the override chain for the equality contract to distinguish record types at run time within the [inheritance hierarchy](../builtin-types/record.md#equality-in-inheritance-hierarchies) (**CS8876**). +- Add a `get` accessor to the `EqualityContract` property. The compiler reads the equality contract at run time to determine whether two record instances are of the same type, so the property must be readable (**CS8906**). ## Positional members -- **CS8866**: *Record member must be a readable instance property or field of the type to match the positional parameter.* -- **CS8907**: *Parameter is unread. Did you forget to use it to initialize the property with that name?* -- **CS8908**: *The type may not be used for a field of a record.* -- **CS8913**: *The positional member found corresponding to this parameter is hidden.* +- **CS8866**: *Record member 'member' must be a readable instance property or field of type 'type' to match positional parameter 'parameter'.* +- **CS8907**: *Parameter 'name' is unread. Did you forget to use it to initialize the property with that name?* +- **CS8908**: *The type 'type' may not be used for a field of a record.* +- **CS8913**: *The positional member 'member' found corresponding to this parameter is hidden.* When you declare a [positional record](../builtin-types/record.md#positional-syntax-for-property-and-field-definition), the compiler synthesizes properties that correspond to each positional parameter. These diagnostics indicate that your explicit declarations conflict with those synthesized properties. For the complete rules, see the [records specification](~/_csharpstandard/standard/classes.md#1516-synthesized-record-class-members) in the C# language specification. To correct these errors, apply the following changes to your positional record declarations: -- Change any explicitly declared member that corresponds to a positional parameter so that it's a readable instance property or field with the same type as the parameter. The compiler needs the member to be readable and type-compatible so that the synthesized `Deconstruct` method and [positional pattern matching](../../fundamentals/functional/pattern-matching.md) can access the value correctly (**CS8866**). -- Ensure that each positional parameter is used to initialize its corresponding property in the constructor body when you provide an explicit constructor. The compiler raises a warning when a parameter goes unused because it typically indicates a typo or a mismatch between the parameter name and the property name, which would leave the property uninitialized (**CS8907**). +- Change any explicitly declared member that corresponds to a positional parameter so it's a readable instance property or field with the same type as the parameter. The compiler needs the member to be readable and type-compatible so that the synthesized `Deconstruct` method and [positional pattern matching](../../fundamentals/functional/pattern-matching.md) can access the value correctly (**CS8866**). +- Ensure that each positional parameter initializes its corresponding property in the constructor body when you provide an explicit constructor. The compiler raises a warning when a parameter goes unused because it typically indicates a typo or a mismatch between the parameter name and the property name, which would leave the property uninitialized (**CS8907**). - Change the type of a field declared in a record to a type that's valid in that context. Certain types, such as `Span` or other `ref struct` types, can't be used as fields in a record because record types require all fields to be compatible with heap allocation and value-based equality (**CS8908**). - Remove the `new` modifier from a member in a derived record that hides a positional member from the base record. When a positional member is hidden, the compiler can't match the positional parameter to its corresponding property, which breaks the synthesized `Deconstruct` method and positional [pattern matching](../../fundamentals/functional/pattern-matching.md) (**CS8913**). ## Equality members -- **CS8851**: *Type defines 'Equals' but not 'GetHashCode'* -- **CS8858**: *The receiver type is not a valid record type and is not a struct type.* +- **CS8851**: *'type' defines 'Equals' but not 'GetHashCode'* +- **CS8857**: *The receiver of a `with` expression must have a non-void type.* +- **CS8858**: *The receiver type 'type' is not a valid record type and is not a struct type.* [Record types](../builtin-types/record.md) provide built-in [value-based equality](../builtin-types/record.md#value-equality). These diagnostics arise when your declarations conflict with the equality contract. For the complete rules on equality, see [equality comparisons](../../programming-guide/statements-expressions-operators/equality-comparisons.md). To correct these errors, apply the following changes: - Add a `GetHashCode` method whenever you define an `Equals` method. The [equality contract](../../programming-guide/statements-expressions-operators/equality-comparisons.md) requires that objects considered equal produce the same hash code, so the compiler enforces that these two methods are always defined together (**CS8851**). -- Change the receiver of a `with` expression so that it's a [record type](../builtin-types/record.md) or a [struct type](../builtin-types/struct.md). The `with` expression creates a modified copy by using the type's copy constructor, which is only available on record types and struct types (**CS8858**). +- Change the receiver of a `with` expression so that it's a [record type](../builtin-types/record.md) or a [struct type](../builtin-types/struct.md). The `with` expression creates a modified copy by using the `record` copy constructor, or value copy semantics for `struct` types (**CS8858**). +- Ensure the receiver of a [`with` expression](../operators/with-expression.md) has a non-void type. The `with` expression produces a new copy of the receiver, so the receiver must evaluate to a value that can be copied (**CS8857**). ## Record inheritance diff --git a/docs/csharp/language-reference/toc.yml b/docs/csharp/language-reference/toc.yml index 8221b7ef8e7bc..e0408b3069312 100644 --- a/docs/csharp/language-reference/toc.yml +++ b/docs/csharp/language-reference/toc.yml @@ -500,8 +500,9 @@ items: href: ./compiler-messages/record-declaration-errors.md displayName: > record, record struct, record class, positional record, - CS8851, CS8858, CS8859, CS8860, CS8864, CS8865, CS8866, CS8870, CS8872, CS8873, - CS8874, CS8875, CS8877, CS8879, CS8907, CS8908, CS8913 + CS8851, CS8857, CS8858, CS8859, CS8860, CS8864, CS8865, CS8866, CS8869, CS8870, + CS8871, CS8872, CS8873, CS8874, CS8875, CS8876, CS8877, CS8879, CS8906, CS8907, + CS8908, CS8913 - name: Union type declarations href: ./compiler-messages/union-declaration-errors.md displayName: > @@ -521,10 +522,12 @@ items: href: ./compiler-messages/property-declaration-errors.md displayName: > property, auto-implemented, init-only, field-backed, - CS0200, CS0545, CS0571, CS0840, CS1014, CS1043, CS8050, CS8051, CS8053, CS8145, - CS8147, CS8341, CS8657, CS8658, CS8659, CS8660, CS8661, CS8664, CS9029, CS9030, - CS9031, CS9032, CS9033, CS9034, CS9035, CS9036, CS9037, CS9038, CS9039, CS9040, - CS9042, CS9045, CS9258, CS9263, CS9264, CS9266, CS9273 + CS0200, CS0273, CS0274, CS0275, CS0276, CS0442, CS0544, CS0545, CS0546, CS0547, + CS0548, CS0571, CS0610, CS0840, CS1014, CS1043, CS1715, CS8050, CS8051, CS8053, + CS8080, CS8145, CS8147, CS8341, CS8657, CS8658, CS8659, CS8660, CS8661, CS8664, + CS8852, CS8853, CS8855, CS8856, CS8903, CS9029, CS9030, CS9031, CS9032, CS9033, + CS9034, CS9035, CS9036, CS9037, CS9038, CS9039, CS9040, CS9042, CS9045, CS9258, + CS9263, CS9264, CS9266, CS9273 - name: Operator declarations href: ./compiler-messages/overloaded-operator-errors.md displayName: > @@ -999,14 +1002,6 @@ items: href: ../misc/cs0271.md - name: CS0272 href: ../misc/cs0272.md - - name: CS0273 - href: ../misc/cs0273.md - - name: CS0274 - href: ../misc/cs0274.md - - name: CS0275 - href: ../misc/cs0275.md - - name: CS0276 - href: ../misc/cs0276.md - name: CS0281 href: ../misc/cs0281.md - name: CS0283 @@ -1031,8 +1026,6 @@ items: href: ./compiler-messages/cs0433.md - name: CS0441 href: ../misc/cs0441.md - - name: CS0442 - href: ../misc/cs0442.md - name: CS0443 href: ../misc/cs0443.md - name: CS0445 @@ -1101,16 +1094,8 @@ items: href: ./compiler-messages/interface-implementation-errors.md - name: CS0542 href: ../misc/cs0542.md - - name: CS0544 - href: ../misc/cs0544.md - name: CS0545 href: ./compiler-messages/property-declaration-errors.md - - name: CS0546 - href: ../misc/cs0546.md - - name: CS0547 - href: ../misc/cs0547.md - - name: CS0548 - href: ../misc/cs0548.md - name: CS0549 href: ../misc/cs0549.md - name: CS0551 @@ -1145,8 +1130,6 @@ items: href: ../misc/cs0596.md - name: CS0601 href: ../misc/cs0601.md - - name: CS0610 - href: ../misc/cs0610.md - name: CS0619 href: ../misc/cs0619.md - name: CS0620 @@ -1481,8 +1464,6 @@ items: href: ./compiler-messages/cs1705.md - name: CS1713 href: ../misc/cs1713.md - - name: CS1715 - href: ../misc/cs1715.md - name: CS1719 href: ../misc/cs1719.md - name: CS1721 @@ -1639,8 +1620,6 @@ items: href: ./compiler-messages/cs8140.md - name: CS8141 href: ./compiler-messages/cs8141.md - - name: CS8145 - href: ./compiler-messages/property-declaration-errors.md - name: CS8146 href: ./compiler-messages/cs8146.md - name: CS8147 diff --git a/docs/csharp/misc/cs0273.md b/docs/csharp/misc/cs0273.md deleted file mode 100644 index c386adf6f060b..0000000000000 --- a/docs/csharp/misc/cs0273.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -description: "Compiler error CS0273" -title: "Compiler error CS0273" -ms.date: 02/11/2019 -f1_keywords: - - "CS0273" -helpviewer_keywords: - - "CS0273" -ms.assetid: 851ad056-feee-48fd-834c-578a1a13e926 ---- -# Compiler error CS0273 - -The accessibility modifier of the 'property_accessor' accessor must be more restrictive than the property or indexer 'property' - -The accessibility modifier of the set/get accessor must be more restrictive than the property or indexer 'property/indexer' - -This error occurs when the accessibility of the accessor you declared isn't less restrictive than the accessibility of the property or indexer. - -## To correct this error - -Use the appropriate access modifier on either the property or the accessor. For more information, see [Restricting Accessor Accessibility](../programming-guide/classes-and-structs/restricting-accessor-accessibility.md) and [Accessors](/dotnet/csharp/language-reference/language-specification/classes#accessors). - -## Example - -This sample contains an internal property with an internal set method. The following sample generates CS0273. - -```csharp -// CS0273.cs -// compile with: /target:library -public class MyClass -{ - internal int Property - { - get { return 0; } - internal set {} // CS0273 - // try the following line instead - // private set {} - } -} -``` diff --git a/docs/csharp/misc/cs0274.md b/docs/csharp/misc/cs0274.md deleted file mode 100644 index 678aedb73b751..0000000000000 --- a/docs/csharp/misc/cs0274.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -description: "Compiler Error CS0274" -title: "Compiler Error CS0274" -ms.date: 07/20/2015 -f1_keywords: - - "CS0274" -helpviewer_keywords: - - "CS0274" -ms.assetid: bbd2d64e-a756-4713-b9ed-711d50b65e00 ---- -# Compiler Error CS0274 - -Cannot specify accessibility modifiers for both accessors of the property or indexer 'property/indexer' - - This error occurs when you declare a property or indexer with access modifiers on both its accessors. To resolve this error, use an access modifier on only one of the two accessors. For more information, see [Accessor Accessibility](../programming-guide/classes-and-structs/restricting-accessor-accessibility.md). - - The following example generates CS0274: - -```csharp -// CS0274.cs -public class MyClass -{ - public int Property // CS0274 - { - public get { return 0; } - protected set { } - } -} -``` diff --git a/docs/csharp/misc/cs0275.md b/docs/csharp/misc/cs0275.md deleted file mode 100644 index ff85e824b38bf..0000000000000 --- a/docs/csharp/misc/cs0275.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -description: "Compiler Error CS0275" -title: "Compiler Error CS0275" -ms.date: 07/20/2015 -f1_keywords: - - "CS0275" -helpviewer_keywords: - - "CS0275" -ms.assetid: 4d59f11c-b0ea-4c91-b2cb-cbe3be9a9ba2 ---- -# Compiler Error CS0275 - -'accessor': accessibility modifiers may not be used on accessors in an interface - - This error occurs when you use an access modifier on any one of the accessors of a property or indexer in an interface. To resolve, remove the access modifier. - -## Example - - The following example generates CS0275: - -```csharp -// CS0275.cs -public interface MyInterface -{ - int Property - { - get; - internal set; // CS0275 - } -} -``` diff --git a/docs/csharp/misc/cs0276.md b/docs/csharp/misc/cs0276.md deleted file mode 100644 index e92af4a8027eb..0000000000000 --- a/docs/csharp/misc/cs0276.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -description: "Compiler Error CS0276" -title: "Compiler Error CS0276" -ms.date: 07/20/2015 -f1_keywords: - - "CS0276" -helpviewer_keywords: - - "CS0276" -ms.assetid: 0c49017f-c7a9-42a5-9d0a-6f1d82142bd7 ---- -# Compiler Error CS0276 - -'property/indexer': accessibility modifiers on accessors may only be used if the property or indexer has both a get and a set accessor - - This error occurs when you declare a property or indexer with one accessor only, and use an access modifier on the accessor. To resolve, remove the access modifier or add another accessor. - -## Example - - The following example generates CS0276: - -```csharp -// CS0276.cs -public class MyClass -{ - public int Property - { - protected set { } // CS0276 - } - public int Property2 - { - internal get { } // CS0276 - } -} -``` diff --git a/docs/csharp/misc/cs0442.md b/docs/csharp/misc/cs0442.md deleted file mode 100644 index 866f11fbd0f51..0000000000000 --- a/docs/csharp/misc/cs0442.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -description: "Compiler Error CS0442" -title: "Compiler Error CS0442" -ms.date: 07/20/2015 -f1_keywords: - - "CS0442" -helpviewer_keywords: - - "CS0442" -ms.assetid: a411660d-0db6-4b63-b19e-f4538fc201e5 ---- -# Compiler Error CS0442 - -'Property': abstract properties cannot have private accessors - - This error occurs when you use the access modifier "private" to modify an abstract accessor. To resolve, use a different access modifier, or make the property non-abstract. - -## Example - - The following sample generates CS0442: - -```csharp -// CS0442.cs -public abstract class MyClass -{ - public abstract int AbstractProperty - { - get; - private set; // CS0442 - // Try this instead: - // set; - } -} -``` diff --git a/docs/csharp/misc/cs0544.md b/docs/csharp/misc/cs0544.md deleted file mode 100644 index 72d92d0227e98..0000000000000 --- a/docs/csharp/misc/cs0544.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -description: "Compiler Error CS0544" -title: "Compiler Error CS0544" -ms.date: 07/20/2015 -f1_keywords: - - "CS0544" -helpviewer_keywords: - - "CS0544" -ms.assetid: f8230a02-a666-4a1a-94cb-46f87463206a ---- -# Compiler Error CS0544 - -'property override': cannot override because 'non-property' is not a property - - An attempt was made to override a nonproperty data type as a [property](../programming-guide/classes-and-structs/properties.md), which is not allowed. - - The following sample generates CS0544: - -```csharp -// CS0544.cs -// compile with: /target:library -public class a -{ - public int i; -} - -public class b : a -{ - public override int i { // CS0544 - // try the following line instead - // public new int i { - get - { - return 0; - } - } -} -``` diff --git a/docs/csharp/misc/cs0546.md b/docs/csharp/misc/cs0546.md deleted file mode 100644 index ac30260b1e775..0000000000000 --- a/docs/csharp/misc/cs0546.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -description: "Compiler Error CS0546" -title: "Compiler Error CS0546" -ms.date: 07/20/2015 -f1_keywords: - - "CS0546" -helpviewer_keywords: - - "CS0546" -ms.assetid: d259c86f-ee29-4e2d-b381-6ba7252af87e ---- -# Compiler Error CS0546 - -'accessor' : cannot override because 'property' does not have an overridable set accessor - - An attempt to override one of the accessor methods for a property failed because the accessor cannot be overridden. This error can occur if: - -- the base class property is not declared as [virtual](../language-reference/keywords/virtual.md). - -- the base class property does not declare the [get](../language-reference/keywords/get.md) or [set](../language-reference/keywords/set.md) accessor you are trying to override. - - If you do not want to override the base class property, you can use the [new](../language-reference/keywords/new-modifier.md) keyword before the property in derived class. - - For more information, see [Using Properties](../programming-guide/classes-and-structs/using-properties.md). - -## Example - - The following sample generates CS0546 because the base class does not declare a set accessor for the property. - -```csharp -// CS0546.cs -// compile with: /target:library -public class a -{ - public virtual int i - { - get - { - return 0; - } - } - - public virtual int i2 - { - get - { - return 0; - } - - set {} - } -} - -public class b : a -{ - public override int i - { - set {} // CS0546 error no set - } - - public override int i2 - { - set {} // OK - } -} -``` - -## See also - -- [Properties](../programming-guide/classes-and-structs/properties.md) diff --git a/docs/csharp/misc/cs0547.md b/docs/csharp/misc/cs0547.md deleted file mode 100644 index cb112225dab7c..0000000000000 --- a/docs/csharp/misc/cs0547.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -description: "Compiler Error CS0547" -title: "Compiler Error CS0547" -ms.date: 07/20/2015 -f1_keywords: - - "CS0547" -helpviewer_keywords: - - "CS0547" -ms.assetid: aa80873f-deb0-4ff2-8435-92a626bb5b80 ---- -# Compiler Error CS0547 - -'property' : property or indexer cannot have void type - - [void](../language-reference/builtin-types/void.md) is invalid as a return value for a property. - - For more information, see [Properties](../programming-guide/classes-and-structs/properties.md). - - The following sample generates CS0547: - -```csharp -// CS0547.cs -public class a -{ - public void i // CS0547 - // Try the following declaration instead: - // public int i - { - get - { - return 0; - } - } -} - -public class b : a -{ - public static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/cs0548.md b/docs/csharp/misc/cs0548.md deleted file mode 100644 index cfc2e69627615..0000000000000 --- a/docs/csharp/misc/cs0548.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -description: "Compiler Error CS0548" -title: "Compiler Error CS0548" -ms.date: 07/20/2015 -f1_keywords: - - "CS0548" -helpviewer_keywords: - - "CS0548" -ms.assetid: c4d34da7-0b4a-4312-ac7f-46db100e43c7 ---- -# Compiler Error CS0548 - -'property' : property or indexer must have at least one accessor - - A property must have at least one accessor (get or set) method. - - For more information, see and [Using Properties](../programming-guide/classes-and-structs/using-properties.md). - -## Example - - The following sample generates CS0548. - -```csharp -// CS0548.cs -// compile with: /target:library -public class b -{ - public int MyProp {} // CS0548 - - public int MyProp2 // OK - { - get - { - return 0; - } - set {} - } -} -``` diff --git a/docs/csharp/misc/cs0610.md b/docs/csharp/misc/cs0610.md deleted file mode 100644 index f3b121546fd4d..0000000000000 --- a/docs/csharp/misc/cs0610.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -description: "Compiler Error CS0610" -title: "Compiler Error CS0610" -ms.date: 07/20/2015 -f1_keywords: - - "CS0610" -helpviewer_keywords: - - "CS0610" -ms.assetid: 6cdce74c-5c00-4539-9df1-32be70e9912d ---- -# Compiler Error CS0610 - -Field or property cannot be of type 'type' - - There are some types that cannot be used as fields or properties. These types include **System.ArgIterator** and **System.TypedReference**. - - The following sample generates CS0610 as a result of using **System.TypedReference** as a field: - -```csharp -// CS0610.cs -public class MainClass -{ - System.TypedReference i; // CS0610 - public static void Main () - { - } - - public static void Test(System.TypedReference i) // OK - { - } -} -``` diff --git a/docs/csharp/misc/cs1715.md b/docs/csharp/misc/cs1715.md deleted file mode 100644 index c9e145c0abc1c..0000000000000 --- a/docs/csharp/misc/cs1715.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -description: "Learn more about: Compiler Error CS1715" -title: "Compiler Error CS1715" -ms.date: 07/20/2015 -f1_keywords: - - "CS1715" -helpviewer_keywords: - - "CS1715" -ms.assetid: 740044d1-a61c-4156-bc6a-adf26c7499ec ---- -# Compiler Error CS1715 - -'Type1': type must be 'Type2' to match overridden member 'MemberName' - - This error is the same as [Compiler Error CS0508](./cs0508.md), except that CS0508 now only applies to methods that have return types, while CS1715 applies to properties and indexers that only have 'types' instead of 'return types'. - -## Example - - The following code generates CS1715. - -```csharp -// CS1715.cs -abstract public class Base -{ - abstract public int myProperty - { - get; - set; - } -} - -public class Derived : Base -{ - int myField; - public override double myProperty // CS1715 - // try the following line instead - // public override int myProperty - { - get { return myField; } - set { myField;= value; } - } - - public static void Main() - { - Derived d = new Derived(); - d.myProperty = 5; - } -} -``` 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 2376d982022a1..f2e9492617f92 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 @@ -156,7 +156,6 @@ f1_keywords: - "CS8077" - "CS8078" # build only diagnostic - "CS8079" - - "CS8080" - "CS8081" - "CS8082" - "CS8084" @@ -301,14 +300,6 @@ f1_keywords: - "CS8823" - "CS8830" # feature / version - "CS8831" - - "CS8852" # init only properties - - "CS8853" - - "CS8855" - - "CS8856" - - "CS8857" # records - - "CS8869" - - "CS8871" - - "CS8876" - "CS8888" # feature / version - "CS8889" - "CS8890" @@ -317,8 +308,6 @@ f1_keywords: - "CS8894" - "CS8895" - "CS8896" - - "CS8903" # init only property - - "CS8906" # record # Coming in C# 15 - "CS9343" # misc - "CS9346" diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/partial-classes-and-methods/Program.cs b/docs/csharp/programming-guide/classes-and-structs/snippets/partial-classes-and-methods/Program.cs index 2a8eff4250eda..986ec7834bf9c 100644 --- a/docs/csharp/programming-guide/classes-and-structs/snippets/partial-classes-and-methods/Program.cs +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/partial-classes-and-methods/Program.cs @@ -179,7 +179,7 @@ public partial class PropertyBag // In file2.cs public partial class PropertyBag { - // Defining declaration + // Implementing declaration public partial int MyProperty { get => field; set; } } diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index 47c2609a1bf59..7429c663cd5f2 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -147,6 +147,8 @@ items: - name: Converting types displayName: cast, is, as href: fundamentals/tutorials/safely-cast-using-pattern-matching-is-and-as-operators.md + - name: Choosing between tuples, records, structs, and classes + href: fundamentals/tutorials/choosing-types.md - name: Build data-driven algorithms with pattern matching href: fundamentals/tutorials/pattern-matching.md - name: Use record types diff --git a/docs/navigate/advanced-programming/toc.yml b/docs/navigate/advanced-programming/toc.yml index 93439663390e4..fc1397e62a59b 100644 --- a/docs/navigate/advanced-programming/toc.yml +++ b/docs/navigate/advanced-programming/toc.yml @@ -40,6 +40,12 @@ items: href: ../../standard/asynchronous-programming-patterns/complete-your-tasks.md - name: Keep async methods alive href: ../../standard/asynchronous-programming-patterns/keep-async-methods-alive.md + - name: Coordination primitives + items: + - name: Build async coordination primitives + href: ../../standard/asynchronous-programming-patterns/async-coordination-primitives.md + - name: Async semaphores, locks, and reader/writer coordination + href: ../../standard/asynchronous-programming-patterns/async-coordination-primitives-advanced.md - name: Event-based asynchronous pattern (EAP) items: - name: Documentation overview diff --git a/docs/orleans/Directory.Build.props b/docs/orleans/Directory.Build.props index 4c687b6fd6730..c7e18ffa1975e 100644 --- a/docs/orleans/Directory.Build.props +++ b/docs/orleans/Directory.Build.props @@ -16,7 +16,7 @@ - + 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 abaefd9fbcdeb..fb9399c7dbf19 100644 --- a/docs/orleans/streaming/snippets/broadcastchannel/BroadcastChannel.Silo/BroadcastChannel.Silo.csproj +++ b/docs/orleans/streaming/snippets/broadcastchannel/BroadcastChannel.Silo/BroadcastChannel.Silo.csproj @@ -6,7 +6,7 @@ - + diff --git a/docs/orleans/tutorials-and-samples/snippets/minimal/Client/Client.csproj b/docs/orleans/tutorials-and-samples/snippets/minimal/Client/Client.csproj index 345d8e6b7a166..270390b52a796 100644 --- a/docs/orleans/tutorials-and-samples/snippets/minimal/Client/Client.csproj +++ b/docs/orleans/tutorials-and-samples/snippets/minimal/Client/Client.csproj @@ -6,7 +6,7 @@ - + diff --git a/docs/orleans/tutorials-and-samples/snippets/minimal/GrainInterfaces/GrainInterfaces.csproj b/docs/orleans/tutorials-and-samples/snippets/minimal/GrainInterfaces/GrainInterfaces.csproj index 4c6c9efd03fc8..6bd1facbba9e7 100644 --- a/docs/orleans/tutorials-and-samples/snippets/minimal/GrainInterfaces/GrainInterfaces.csproj +++ b/docs/orleans/tutorials-and-samples/snippets/minimal/GrainInterfaces/GrainInterfaces.csproj @@ -2,8 +2,8 @@ - - + + diff --git a/docs/orleans/tutorials-and-samples/snippets/minimal/Grains/Grains.csproj b/docs/orleans/tutorials-and-samples/snippets/minimal/Grains/Grains.csproj index 6573c7d90c21a..9e18e4c2cf74d 100644 --- a/docs/orleans/tutorials-and-samples/snippets/minimal/Grains/Grains.csproj +++ b/docs/orleans/tutorials-and-samples/snippets/minimal/Grains/Grains.csproj @@ -2,7 +2,7 @@ - + diff --git a/docs/orleans/tutorials-and-samples/snippets/minimal/Silo/Silo.csproj b/docs/orleans/tutorials-and-samples/snippets/minimal/Silo/Silo.csproj index b22c71883b4b6..f25ea59515edc 100644 --- a/docs/orleans/tutorials-and-samples/snippets/minimal/Silo/Silo.csproj +++ b/docs/orleans/tutorials-and-samples/snippets/minimal/Silo/Silo.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/docs/standard/asynchronous-programming-patterns/async-coordination-primitives-advanced.md b/docs/standard/asynchronous-programming-patterns/async-coordination-primitives-advanced.md new file mode 100644 index 0000000000000..b57ee3ea88b9d --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/async-coordination-primitives-advanced.md @@ -0,0 +1,97 @@ +--- +title: "Async semaphores, locks, and reader/writer coordination" +description: Learn to use SemaphoreSlim.WaitAsync for async throttling, build async locks for mutual exclusion, and coordinate readers and writers in async code. +ms.date: 04/16/2026 +ai-usage: ai-assisted +dev_langs: + - "csharp" + - "vb" +--- + +# Async semaphores, locks, and reader/writer coordination + +When async code needs throttling, mutual exclusion, or reader/writer coordination, use the built-in .NET types rather than building your own. This article shows how to apply those types, and then walks through custom implementations to explain how they work internally. + +## Async semaphore — throttle concurrent access + +A semaphore limits how many callers can access a resource concurrently. provides a method that lets you await entry without blocking a thread: + +:::code language="csharp" source="./snippets/async-coordination-primitives-advanced/csharp/Program.cs" id="SemaphoreSlimUsage"::: +:::code language="vb" source="./snippets/async-coordination-primitives-advanced/vb/Program.vb" id="SemaphoreSlimUsage"::: + +Always pair `WaitAsync` with `Release` in a `try`/`finally` block. If you forget to release, the semaphore count never increases, and other callers wait indefinitely. + +### How an async semaphore works + +Internally, an async semaphore maintains a count and a queue of waiters. When the count is above zero, `WaitAsync` decrements the count and returns immediately. When the count is zero, `WaitAsync` enqueues a and returns its task. `Release` either dequeues a waiter and completes it, or increments the count: + +:::code language="csharp" source="./snippets/async-coordination-primitives-advanced/csharp/Program.cs" id="AsyncSemaphore"::: +:::code language="vb" source="./snippets/async-coordination-primitives-advanced/vb/Program.vb" id="AsyncSemaphore"::: + +The `Release` method completes the `TaskCompletionSource` outside the lock, just like the `AsyncAutoResetEvent` in [Build async coordination primitives](async-coordination-primitives.md). This approach prevents synchronous continuations from running while the lock is held. + +> [!NOTE] +> `AsyncSemaphore` is an educational implementation. Use instead—it supports cancellation tokens, timeouts, and has been thoroughly tested. + +## Async lock: mutual exclusion across awaits + +A lock with a count of 1 provides mutual exclusion. The C# `lock` statement and (.NET 9+) don't work across `await` boundaries because they're thread-affine. A *thread-affine* lock the same thread that acquires the lock must be the one that releases it. Across an `await`, the thread that resumes the continuation might not be the thread that acquired the lock, which violates that requirement. Use with a count of 1 instead: + +:::code language="csharp" source="./snippets/async-coordination-primitives-advanced/csharp/Program.cs" id="SemaphoreSlimAsLock"::: +:::code language="vb" source="./snippets/async-coordination-primitives-advanced/vb/Program.vb" id="SemaphoreSlimAsLock"::: + +### How an async lock works + +You can wrap the semaphore pattern in a type that supports `using` for automatic release. The `LockAsync` method returns a disposable `Releaser`; when the `Releaser` is disposed, it releases the semaphore: + +:::code language="csharp" source="./snippets/async-coordination-primitives-advanced/csharp/Program.cs" id="AsyncLock"::: +:::code language="vb" source="./snippets/async-coordination-primitives-advanced/vb/Program.vb" id="AsyncLock"::: + +Usage is concise and safe: + +:::code language="csharp" source="./snippets/async-coordination-primitives-advanced/csharp/Program.cs" id="AsyncLockUsage"::: +:::code language="vb" source="./snippets/async-coordination-primitives-advanced/vb/Program.vb" id="AsyncLockUsage"::: + +> [!NOTE] +> `AsyncLock` is an educational implementation. Use initialized to `1` with `try`/`finally` directly—the `AsyncLock` type shown here illustrates the disposable-releaser pattern but adds no capabilities beyond what `SemaphoreSlim` provides. + +## Async reader/writer coordination + +A reader/writer lock allows multiple concurrent readers but only one exclusive writer. .NET provides , which offers reader/writer scheduling for tasks through two instances: + +- — runs tasks concurrently (like readers), as long as no exclusive task is active. +- — runs tasks exclusively (like writers), with no other tasks running. + +:::code language="csharp" source="./snippets/async-coordination-primitives-advanced/csharp/Program.cs" id="ConcurrentExclusiveUsage"::: +:::code language="vb" source="./snippets/async-coordination-primitives-advanced/vb/Program.vb" id="ConcurrentExclusiveUsage"::: + +> [!IMPORTANT] +> `ConcurrentExclusiveSchedulerPair` protects at the task level, not across `await` boundaries. If a task queued to the `ExclusiveScheduler` contains an `await` on an incomplete operation, the exclusive lock releases when the `await` yields and reacquires when the continuation runs. Another exclusive or concurrent task can run during that gap. This behavior works well when you protect in-memory data structures and ensure no `await` interrupts the critical section. For scenarios that require holding the lock across awaits, use a custom `AsyncReaderWriterLock` like the one shown in the following section. + +### Custom async reader/writer lock + +The following implementation gives writers priority over readers. When a writer is waiting, new readers queue behind it. When a writer finishes and no other writers are waiting, all queued readers run together: + +:::code language="csharp" source="./snippets/async-coordination-primitives-advanced/csharp/Program.cs" id="AsyncReaderWriterLock"::: +:::code language="vb" source="./snippets/async-coordination-primitives-advanced/vb/Program.vb" id="AsyncReaderWriterLock"::: + +Usage follows the same disposable-releaser pattern as `AsyncLock`: + +:::code language="csharp" source="./snippets/async-coordination-primitives-advanced/csharp/Program.cs" id="AsyncReaderWriterLockUsage"::: +:::code language="vb" source="./snippets/async-coordination-primitives-advanced/vb/Program.vb" id="AsyncReaderWriterLockUsage"::: + +> [!TIP] +> A production reader/writer lock requires thorough testing for edge cases: reentrancy, error paths, cancellation, and fairness policies. Consider established libraries (such as [Nito.AsyncEx](https://github.com/StephenCleary/AsyncEx)) before building your own. + +## Channels as an alternative coordination pattern + + provides a thread-safe producer-consumer queue that supports `async` reads and writes. Bounded channels () provide natural back-pressure, replacing some scenarios where you'd otherwise use a semaphore for throttling. + +For more information, see [System.Threading.Channels](/dotnet/core/extensions/channels). + +## See also + +- [Build async coordination primitives](async-coordination-primitives.md) +- [Keep async methods alive](keep-async-methods-alive.md) +- [Complete your tasks](complete-your-tasks.md) +- [Consuming the Task-based Asynchronous Pattern](consuming-the-task-based-asynchronous-pattern.md) diff --git a/docs/standard/asynchronous-programming-patterns/async-coordination-primitives.md b/docs/standard/asynchronous-programming-patterns/async-coordination-primitives.md new file mode 100644 index 0000000000000..1e08c80d4f33a --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/async-coordination-primitives.md @@ -0,0 +1,104 @@ +--- +title: "Build async coordination primitives" +description: Learn how to build async coordination primitives using TaskCompletionSource, including manual-reset events, auto-reset events, countdown events, and barriers. +ms.date: 04/16/2026 +ai-usage: ai-assisted +dev_langs: + - "csharp" + - "vb" +helpviewer_keywords: + - "async coordination" + - "TaskCompletionSource" + - "AsyncManualResetEvent" + - "AsyncAutoResetEvent" + - "AsyncCountdownEvent" + - "AsyncBarrier" + - "coordination primitives" +--- + +# Build async coordination primitives + +Synchronous coordination primitives like , , and block the calling thread while waiting. In async code, blocking a thread wastes a resource that could be doing other work. Use to build async equivalents that let callers `await` instead of blocking. + +A `TaskCompletionSource` produces a that you complete manually by calling , , or . Code that awaits that task suspends without blocking a thread and resumes when you complete the source. This pattern forms the building block for every primitive in this article. + +> [!NOTE] +> The primitives in this article are educational implementations. For production throttling and mutual exclusion, use the built-in types covered in [Async semaphores, locks, and reader/writer coordination](async-coordination-primitives-advanced.md). Always complete every `TaskCompletionSource` you create; see [Complete your tasks](complete-your-tasks.md) for guidance. + +## Async manual-reset event + +A manual-reset event starts in a non-signaled state. Callers wait for the event, and all waiters resume when another party signals (sets) the event. The event stays signaled until you explicitly reset it. The synchronous equivalent is . The .NET runtime provides directly for one-shot broadcast signaling. Create a new instance each cycle rather than building a reset wrapper around it. + +`TaskCompletionSource` is itself a one-shot manual-reset event: its `Task` is incomplete until you call a `Set*` method, and then all awaiters resume. Add a `Reset` method that swaps in a new `TaskCompletionSource`, and you have a reusable async manual-reset event. + +:::code language="csharp" source="./snippets/async-coordination-primitives/csharp/Program.cs" id="AsyncManualResetEvent"::: +:::code language="vb" source="./snippets/async-coordination-primitives/vb/Program.vb" id="AsyncManualResetEvent"::: + +Key implementation details: + +- The constructor passes to prevent `Set` from running waiter continuations synchronously on the calling thread. Without this flag, `Set` could block for an unpredictable amount of time. +- `Reset` uses to swap in a new `TaskCompletionSource` only when the current one is already completed. This atomic swap prevents orphaning a task that a waiter already received. + +The following example shows how two tasks coordinate through the event: + +:::code language="csharp" source="./snippets/async-coordination-primitives/csharp/Program.cs" id="AsyncManualResetEventUsage"::: +:::code language="vb" source="./snippets/async-coordination-primitives/vb/Program.vb" id="AsyncManualResetEventUsage"::: + +## Async auto-reset event + +An auto-reset event is similar to a manual-reset event, but it automatically returns to the non-signaled state after releasing exactly one waiter. If multiple callers are waiting when the event is signaled, only one waiter resumes. The synchronous equivalent is . The .NET runtime includes for single-waiter async signaling. Initialize it to `0` with a maximum count of `1` and call `WaitAsync` to wait and `Release` to signal. + +Because each signal releases only one waiter, you need a collection of `TaskCompletionSource` instances—one per waiter—so you can complete them individually: + +:::code language="csharp" source="./snippets/async-coordination-primitives/csharp/Program.cs" id="AsyncAutoResetEvent"::: +:::code language="vb" source="./snippets/async-coordination-primitives/vb/Program.vb" id="AsyncAutoResetEvent"::: + +Key implementation details: + +- The `Set` method completes the `TaskCompletionSource` (TCS) *outside* the lock. Completing a TCS inside the lock runs synchronous continuations while the lock is held, which could cause deadlocks or unexpected reentrancy. +- When `Set` is called and no waiter is queued, the signal is stored so the next `WaitAsync` call completes immediately. + +The following example shows a producer signaling a consumer through the event: + +:::code language="csharp" source="./snippets/async-coordination-primitives/csharp/Program.cs" id="AsyncAutoResetEventUsage"::: +:::code language="vb" source="./snippets/async-coordination-primitives/vb/Program.vb" id="AsyncAutoResetEventUsage"::: + +## Async countdown event + +A countdown event waits for a specified number of signals before it allows waiters to proceed. This pattern is useful for fork/join scenarios where you start N operations and want to await all N completions. The synchronous equivalent is . The .NET runtime provides for fork/join coordination with a fixed set of tasks. Use it instead. + +Build the async version by composing the `AsyncManualResetEvent` from the previous section with an atomic counter: + +:::code language="csharp" source="./snippets/async-coordination-primitives/csharp/Program.cs" id="AsyncCountdownEvent"::: +:::code language="vb" source="./snippets/async-coordination-primitives/vb/Program.vb" id="AsyncCountdownEvent"::: + +The `Signal` method decrements the count atomically with . When the count reaches zero, it sets the inner event, and all waiters resume. + +The following example uses a countdown event to await three concurrent operations: + +:::code language="csharp" source="./snippets/async-coordination-primitives/csharp/Program.cs" id="AsyncCountdownEventUsage"::: +:::code language="vb" source="./snippets/async-coordination-primitives/vb/Program.vb" id="AsyncCountdownEventUsage"::: + +## Async barrier + +A barrier coordinates a fixed set of participants across multiple rounds. Each participant signals when it finishes its work for the current round and then waits for all other participants to finish. When the last participant signals, all participants resume, and the barrier resets for the next round. The synchronous equivalent is . The .NET runtime provides for multi-round async synchronization. Combine it with a loop, one `WhenAll` call per round. + +:::code language="csharp" source="./snippets/async-coordination-primitives/csharp/Program.cs" id="AsyncBarrier"::: +:::code language="vb" source="./snippets/async-coordination-primitives/vb/Program.vb" id="AsyncBarrier"::: + +Key implementation details: + +- Before completing the shared `TaskCompletionSource`, the method resets the count and swaps in a new `TaskCompletionSource` for the next round. This ordering ensures that when waiters resume, the barrier is already ready for the next round. +- All participants share the same `Task`. Because the sample creates the `TaskCompletionSource` with `RunContinuationsAsynchronously`, continuations resume asynchronously instead of running inline on the thread that completes the barrier. + +The following example runs three participants through two rounds of a barrier: + +:::code language="csharp" source="./snippets/async-coordination-primitives/csharp/Program.cs" id="AsyncBarrierUsage"::: +:::code language="vb" source="./snippets/async-coordination-primitives/vb/Program.vb" id="AsyncBarrierUsage"::: + +## See also + +- [Async semaphores, locks, and reader/writer coordination](async-coordination-primitives-advanced.md) +- [Task-based asynchronous pattern (TAP)](task-based-asynchronous-pattern-tap.md) +- [Complete your tasks](complete-your-tasks.md) +- [Keep async methods alive](keep-async-methods-alive.md) diff --git a/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives-advanced/csharp/AsyncCoordinationPrimitivesAdvanced.csproj b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives-advanced/csharp/AsyncCoordinationPrimitivesAdvanced.csproj new file mode 100644 index 0000000000000..dfb40caafcf9a --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives-advanced/csharp/AsyncCoordinationPrimitivesAdvanced.csproj @@ -0,0 +1,10 @@ + + + + Exe + net10.0 + enable + enable + + + diff --git a/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives-advanced/csharp/Program.cs b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives-advanced/csharp/Program.cs new file mode 100644 index 0000000000000..e47b477c2a01e --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives-advanced/csharp/Program.cs @@ -0,0 +1,378 @@ +using System.Collections.Concurrent; + +// +public static class SemaphoreSlimDemo +{ + public static async Task RunAsync() + { + using var semaphore = new SemaphoreSlim(3); + + Task[] tasks = Enumerable.Range(1, 6).Select(id => Task.Run(async () => + { + await semaphore.WaitAsync(); + try + { + Console.WriteLine($"Task {id}: entered (count = {semaphore.CurrentCount})"); + await Task.Delay(100); + } + finally + { + semaphore.Release(); + Console.WriteLine($"Task {id}: released"); + } + })).ToArray(); + + await Task.WhenAll(tasks); + } +} +// + +// +// Educational only — use SemaphoreSlim instead of this sample implementation. +public class AsyncSemaphore +{ + private readonly Queue _waiters = new(); + private int _currentCount; + + public AsyncSemaphore(int initialCount) + { + ArgumentOutOfRangeException.ThrowIfNegative(initialCount, nameof(initialCount)); + _currentCount = initialCount; + } + + public Task WaitAsync() + { + lock (_waiters) + { + if (_currentCount > 0) + { + _currentCount--; + return Task.CompletedTask; + } + else + { + var waiter = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _waiters.Enqueue(waiter); + return waiter.Task; + } + } + } + + public void Release() + { + TaskCompletionSource? toRelease = null; + + lock (_waiters) + { + if (_waiters.Count > 0) + toRelease = _waiters.Dequeue(); + else + _currentCount++; + } + + toRelease?.TrySetResult(); + } +} +// + +// +public static class SemaphoreSlimAsLockDemo +{ + private static readonly SemaphoreSlim s_lock = new(1, 1); + private static int s_sharedCounter; + + public static async Task RunAsync() + { + Task[] tasks = Enumerable.Range(1, 5).Select(_ => Task.Run(async () => + { + await s_lock.WaitAsync(); + try + { + int before = s_sharedCounter; + await Task.Delay(10); + s_sharedCounter = before + 1; + } + finally + { + s_lock.Release(); + } + })).ToArray(); + + await Task.WhenAll(tasks); + Console.WriteLine($"Counter = {s_sharedCounter} (expected 5)"); + } +} +// + +// +// Educational only — use SemaphoreSlim(1, 1) with try/finally instead of this sample implementation. +public class AsyncLock : IDisposable +{ + private readonly SemaphoreSlim _semaphore = new(1, 1); + private readonly Task _releaser; + + public AsyncLock() + { + _releaser = Task.FromResult(new Releaser(this)); + } + + public Task LockAsync() + { + Task wait = _semaphore.WaitAsync(); + return wait.IsCompleted + ? _releaser + : wait.ContinueWith( + (_, state) => new Releaser((AsyncLock)state!), + this, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + + public struct Releaser : IDisposable + { + private readonly AsyncLock? _toRelease; + + internal Releaser(AsyncLock toRelease) => _toRelease = toRelease; + + public void Dispose() => _toRelease?._semaphore.Release(); + } + + public void Dispose() => _semaphore.Dispose(); +} +// + +// +public static class AsyncLockDemo +{ + private static readonly AsyncLock s_lock = new(); + private static int s_sharedValue; + + public static async Task RunAsync() + { + Task[] tasks = Enumerable.Range(1, 5).Select(id => Task.Run(async () => + { + using (await s_lock.LockAsync()) + { + int before = s_sharedValue; + await Task.Delay(10); + s_sharedValue = before + 1; + Console.WriteLine($"Task {id}: incremented to {s_sharedValue}"); + } + })).ToArray(); + + await Task.WhenAll(tasks); + Console.WriteLine($"Final value = {s_sharedValue} (expected 5)"); + } +} +// + +// +public static class ConcurrentExclusiveDemo +{ + public static async Task RunAsync() + { + var pair = new ConcurrentExclusiveSchedulerPair(); + var factory = new TaskFactory(pair.ExclusiveScheduler); + + int sharedValue = 0; + + Task writerTask = factory.StartNew(() => + { + sharedValue = 42; + Console.WriteLine($"Writer: set value to {sharedValue}"); + }); + + var readerFactory = new TaskFactory(pair.ConcurrentScheduler); + + Task[] readerTasks = Enumerable.Range(1, 3).Select(id => + readerFactory.StartNew(() => + { + Console.WriteLine($"Reader {id}: value = {sharedValue}"); + })).ToArray(); + + await writerTask; + await Task.WhenAll(readerTasks); + } +} +// + +// +public class AsyncReaderWriterLock +{ + private readonly Queue> _waitingWriters = new(); + private TaskCompletionSource _waitingReader = + new(TaskCreationOptions.RunContinuationsAsynchronously); + private int _readersWaiting; + private int _status; // 0 = free, -1 = writer active, >0 = reader count + + private readonly Task _readerReleaser; + private readonly Task _writerReleaser; + + public AsyncReaderWriterLock() + { + _readerReleaser = Task.FromResult(new Releaser(this, isWriter: false)); + _writerReleaser = Task.FromResult(new Releaser(this, isWriter: true)); + } + + public Task ReaderLockAsync() + { + lock (_waitingWriters) + { + if (_status >= 0 && _waitingWriters.Count == 0) + { + _status++; + return _readerReleaser; + } + else + { + _readersWaiting++; + return _waitingReader.Task; + } + } + } + + public Task WriterLockAsync() + { + lock (_waitingWriters) + { + if (_status == 0) + { + _status = -1; + return _writerReleaser; + } + else + { + var waiter = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _waitingWriters.Enqueue(waiter); + return waiter.Task; + } + } + } + + private void ReaderRelease() + { + TaskCompletionSource? toWake = null; + + lock (_waitingWriters) + { + _status--; + if (_status == 0 && _waitingWriters.Count > 0) + { + _status = -1; + toWake = _waitingWriters.Dequeue(); + } + } + + toWake?.SetResult(new Releaser(this, isWriter: true)); + } + + private void WriterRelease() + { + TaskCompletionSource? toWake = null; + bool toWakeIsWriter = false; + + lock (_waitingWriters) + { + if (_waitingWriters.Count > 0) + { + toWake = _waitingWriters.Dequeue(); + toWakeIsWriter = true; + } + else if (_readersWaiting > 0) + { + toWake = _waitingReader; + _status = _readersWaiting; + _readersWaiting = 0; + _waitingReader = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + else + { + _status = 0; + } + } + + toWake?.SetResult(new Releaser(this, toWakeIsWriter)); + } + + public struct Releaser : IDisposable + { + private readonly AsyncReaderWriterLock? _lock; + private readonly bool _isWriter; + + internal Releaser(AsyncReaderWriterLock lockObj, bool isWriter) + { + _lock = lockObj; + _isWriter = isWriter; + } + + public void Dispose() + { + if (_lock is not null) + { + if (_isWriter) _lock.WriterRelease(); + else _lock.ReaderRelease(); + } + } + } +} +// + +// +public static class AsyncReaderWriterLockDemo +{ + private static readonly AsyncReaderWriterLock s_rwLock = new(); + private static string s_data = "initial"; + + public static async Task RunAsync() + { + Task writer = Task.Run(async () => + { + using (await s_rwLock.WriterLockAsync()) + { + Console.WriteLine("Writer: acquired exclusive lock"); + await Task.Delay(50); + s_data = "updated"; + Console.WriteLine("Writer: data updated"); + } + }); + + Task[] readers = Enumerable.Range(1, 3).Select(id => Task.Run(async () => + { + await Task.Delay(10); + using (await s_rwLock.ReaderLockAsync()) + { + Console.WriteLine($"Reader {id}: data = {s_data}"); + } + })).ToArray(); + + await writer; + await Task.WhenAll(readers); + } +} +// + +public static class Program +{ + public static async Task Main() + { + Console.WriteLine("--- SemaphoreSlim ---"); + await SemaphoreSlimDemo.RunAsync(); + + Console.WriteLine(); + Console.WriteLine("--- SemaphoreSlim as lock ---"); + await SemaphoreSlimAsLockDemo.RunAsync(); + + Console.WriteLine(); + Console.WriteLine("--- AsyncLock ---"); + await AsyncLockDemo.RunAsync(); + + Console.WriteLine(); + Console.WriteLine("--- ConcurrentExclusiveSchedulerPair ---"); + await ConcurrentExclusiveDemo.RunAsync(); + + Console.WriteLine(); + Console.WriteLine("--- AsyncReaderWriterLock ---"); + await AsyncReaderWriterLockDemo.RunAsync(); + } +} diff --git a/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives-advanced/vb/AsyncCoordinationPrimitivesAdvanced.vbproj b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives-advanced/vb/AsyncCoordinationPrimitivesAdvanced.vbproj new file mode 100644 index 0000000000000..219b1b9e39ace --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives-advanced/vb/AsyncCoordinationPrimitivesAdvanced.vbproj @@ -0,0 +1,9 @@ + + + + Exe + AsyncCoordinationPrimitivesAdvanced + net10.0 + + + diff --git a/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives-advanced/vb/Program.vb b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives-advanced/vb/Program.vb new file mode 100644 index 0000000000000..1b992c570203c --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives-advanced/vb/Program.vb @@ -0,0 +1,334 @@ +Imports System.Collections.Concurrent +Imports System.Threading + +' +Public Module SemaphoreSlimDemo + Public Async Function RunAsync() As Task + Using semaphore As New SemaphoreSlim(3) + Dim tasks As Task() = Enumerable.Range(1, 6).Select( + Function(id) Task.Run(Async Function() + Await semaphore.WaitAsync() + Try + Console.WriteLine($"Task {id}: entered (count = {semaphore.CurrentCount})") + Await Task.Delay(100) + Finally + semaphore.Release() + Console.WriteLine($"Task {id}: released") + End Try + End Function)).ToArray() + + Await Task.WhenAll(tasks) + End Using + End Function +End Module +' + +' +' Educational only — use SemaphoreSlim instead of this sample implementation. +Public Class AsyncSemaphore + Private ReadOnly _waiters As New Queue(Of TaskCompletionSource)() + Private _currentCount As Integer + + Public Sub New(initialCount As Integer) + If initialCount < 0 Then Throw New ArgumentOutOfRangeException(NameOf(initialCount)) + _currentCount = initialCount + End Sub + + Public Function WaitAsync() As Task + SyncLock _waiters + If _currentCount > 0 Then + _currentCount -= 1 + Return Task.CompletedTask + Else + Dim waiter As New TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously) + _waiters.Enqueue(waiter) + Return waiter.Task + End If + End SyncLock + End Function + + Public Sub Release() + Dim toRelease As TaskCompletionSource = Nothing + + SyncLock _waiters + If _waiters.Count > 0 Then + toRelease = _waiters.Dequeue() + Else + _currentCount += 1 + End If + End SyncLock + + toRelease?.TrySetResult() + End Sub +End Class +' + +' +Public Module SemaphoreSlimAsLockDemo + Private ReadOnly s_lock As New SemaphoreSlim(1, 1) + Private s_sharedCounter As Integer + + Public Async Function RunAsync() As Task + Dim tasks As Task() = Enumerable.Range(1, 5).Select( + Function(unused) Task.Run(Async Function() + Await s_lock.WaitAsync() + Try + Dim before As Integer = s_sharedCounter + Await Task.Delay(10) + s_sharedCounter = before + 1 + Finally + s_lock.Release() + End Try + End Function)).ToArray() + + Await Task.WhenAll(tasks) + Console.WriteLine($"Counter = {s_sharedCounter} (expected 5)") + End Function +End Module +' + +' +' Educational only — use SemaphoreSlim(1, 1) with Try/Finally instead of this sample implementation. +Public Class AsyncLock + Implements IDisposable + + Private ReadOnly _semaphore As New SemaphoreSlim(1, 1) + Private ReadOnly _releaser As Task(Of Releaser) + + Public Sub New() + _releaser = Task.FromResult(New Releaser(Me)) + End Sub + + Public Function LockAsync() As Task(Of Releaser) + Dim wait As Task = _semaphore.WaitAsync() + If wait.IsCompleted Then + Return _releaser + Else + Return wait.ContinueWith( + Function(unused, state) New Releaser(DirectCast(state, AsyncLock)), + Me, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default) + End If + End Function + + Public Structure Releaser + Implements IDisposable + + Private ReadOnly _toRelease As AsyncLock + + Friend Sub New(toRelease As AsyncLock) + _toRelease = toRelease + End Sub + + Public Sub Dispose() Implements IDisposable.Dispose + _toRelease?._semaphore.Release() + End Sub + End Structure + + Public Sub Dispose() Implements IDisposable.Dispose + _semaphore.Dispose() + End Sub +End Class +' + +' +Public Module AsyncLockDemo + Private ReadOnly s_lock As New AsyncLock() + Private s_sharedValue As Integer + + Public Async Function RunAsync() As Task + Dim tasks As Task() = Enumerable.Range(1, 5).Select( + Function(id) Task.Run(Async Function() + Using Await s_lock.LockAsync() + Dim before As Integer = s_sharedValue + Await Task.Delay(10) + s_sharedValue = before + 1 + Console.WriteLine($"Task {id}: incremented to {s_sharedValue}") + End Using + End Function)).ToArray() + + Await Task.WhenAll(tasks) + Console.WriteLine($"Final value = {s_sharedValue} (expected 5)") + End Function +End Module +' + +' +Public Module ConcurrentExclusiveDemo + Public Async Function RunAsync() As Task + Dim pair As New ConcurrentExclusiveSchedulerPair() + Dim exclusiveFactory As New TaskFactory(pair.ExclusiveScheduler) + + Dim sharedValue As Integer = 0 + + Dim writerTask As Task = exclusiveFactory.StartNew(Sub() + sharedValue = 42 + Console.WriteLine($"Writer: set value to {sharedValue}") + End Sub) + + Dim readerFactory As New TaskFactory(pair.ConcurrentScheduler) + + Dim readerTasks As Task() = Enumerable.Range(1, 3).Select( + Function(id) readerFactory.StartNew(Sub() + Console.WriteLine($"Reader {id}: value = {sharedValue}") + End Sub)).ToArray() + + Await writerTask + Await Task.WhenAll(readerTasks) + End Function +End Module +' + +' +Public Class AsyncReaderWriterLock + Private ReadOnly _waitingWriters As New Queue(Of TaskCompletionSource(Of Releaser))() + Private _waitingReader As New TaskCompletionSource(Of Releaser)(TaskCreationOptions.RunContinuationsAsynchronously) + Private _readersWaiting As Integer + Private _status As Integer ' 0 = free, -1 = writer active, >0 = reader count + + Private ReadOnly _readerReleaser As Task(Of Releaser) + Private ReadOnly _writerReleaser As Task(Of Releaser) + + Public Sub New() + _readerReleaser = Task.FromResult(New Releaser(Me, isWriter:=False)) + _writerReleaser = Task.FromResult(New Releaser(Me, isWriter:=True)) + End Sub + + Public Function ReaderLockAsync() As Task(Of Releaser) + SyncLock _waitingWriters + If _status >= 0 AndAlso _waitingWriters.Count = 0 Then + _status += 1 + Return _readerReleaser + Else + _readersWaiting += 1 + Return _waitingReader.Task + End If + End SyncLock + End Function + + Public Function WriterLockAsync() As Task(Of Releaser) + SyncLock _waitingWriters + If _status = 0 Then + _status = -1 + Return _writerReleaser + Else + Dim waiter As New TaskCompletionSource(Of Releaser)( + System.Threading.Tasks.TaskCreationOptions.RunContinuationsAsynchronously) + _waitingWriters.Enqueue(waiter) + Return waiter.Task + End If + End SyncLock + End Function + + Private Sub ReaderRelease() + Dim toWake As TaskCompletionSource(Of Releaser) = Nothing + + SyncLock _waitingWriters + _status -= 1 + If _status = 0 AndAlso _waitingWriters.Count > 0 Then + _status = -1 + toWake = _waitingWriters.Dequeue() + End If + End SyncLock + + toWake?.SetResult(New Releaser(Me, isWriter:=True)) + End Sub + + Private Sub WriterRelease() + Dim toWake As TaskCompletionSource(Of Releaser) = Nothing + Dim toWakeIsWriter As Boolean = False + + SyncLock _waitingWriters + If _waitingWriters.Count > 0 Then + toWake = _waitingWriters.Dequeue() + toWakeIsWriter = True + ElseIf _readersWaiting > 0 Then + toWake = _waitingReader + _status = _readersWaiting + _readersWaiting = 0 + _waitingReader = New TaskCompletionSource(Of Releaser)(TaskCreationOptions.RunContinuationsAsynchronously) + Else + _status = 0 + End If + End SyncLock + + toWake?.SetResult(New Releaser(Me, toWakeIsWriter)) + End Sub + + Public Structure Releaser + Implements IDisposable + + Private ReadOnly _lock As AsyncReaderWriterLock + Private ReadOnly _isWriter As Boolean + + Friend Sub New(lockObj As AsyncReaderWriterLock, isWriter As Boolean) + _lock = lockObj + _isWriter = isWriter + End Sub + + Public Sub Dispose() Implements IDisposable.Dispose + If _lock IsNot Nothing Then + If _isWriter Then + _lock.WriterRelease() + Else + _lock.ReaderRelease() + End If + End If + End Sub + End Structure +End Class +' + +' +Public Module AsyncReaderWriterLockDemo + Private ReadOnly s_rwLock As New AsyncReaderWriterLock() + Private s_data As String = "initial" + + Public Async Function RunAsync() As Task + Dim writer As Task = Task.Run(Async Function() + Using Await s_rwLock.WriterLockAsync() + Console.WriteLine("Writer: acquired exclusive lock") + Await Task.Delay(50) + s_data = "updated" + Console.WriteLine("Writer: data updated") + End Using + End Function) + + Dim readers As Task() = Enumerable.Range(1, 3).Select( + Function(id) Task.Run(Async Function() + Await Task.Delay(10) + Using Await s_rwLock.ReaderLockAsync() + Console.WriteLine($"Reader {id}: data = {s_data}") + End Using + End Function)).ToArray() + + Await writer + Await Task.WhenAll(readers) + End Function +End Module +' + +Module Program + Sub Main() + Console.WriteLine("--- SemaphoreSlim ---") + SemaphoreSlimDemo.RunAsync().Wait() + + Console.WriteLine() + Console.WriteLine("--- SemaphoreSlim as lock ---") + SemaphoreSlimAsLockDemo.RunAsync().Wait() + + Console.WriteLine() + Console.WriteLine("--- AsyncLock ---") + AsyncLockDemo.RunAsync().Wait() + + Console.WriteLine() + Console.WriteLine("--- ConcurrentExclusiveSchedulerPair ---") + ConcurrentExclusiveDemo.RunAsync().Wait() + + Console.WriteLine() + Console.WriteLine("--- AsyncReaderWriterLock ---") + AsyncReaderWriterLockDemo.RunAsync().Wait() + End Sub +End Module diff --git a/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives/csharp/AsyncCoordinationPrimitives.csproj b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives/csharp/AsyncCoordinationPrimitives.csproj new file mode 100644 index 0000000000000..dfb40caafcf9a --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives/csharp/AsyncCoordinationPrimitives.csproj @@ -0,0 +1,10 @@ + + + + Exe + net10.0 + enable + enable + + + diff --git a/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives/csharp/Program.cs b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives/csharp/Program.cs new file mode 100644 index 0000000000000..d1991b8f719a6 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives/csharp/Program.cs @@ -0,0 +1,260 @@ +// +// Educational only — use TaskCompletionSource directly instead of this sample implementation; create a new instance each cycle. +public class AsyncManualResetEvent +{ + private volatile TaskCompletionSource _tcs = new(TaskCreationOptions.RunContinuationsAsynchronously); + + public Task WaitAsync() => _tcs.Task; + + public void Set() => _tcs.TrySetResult(); + + public void Reset() + { + while (true) + { + TaskCompletionSource tcs = _tcs; + if (!tcs.Task.IsCompleted || + Interlocked.CompareExchange( + ref _tcs, + new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously), + tcs) == tcs) + { + return; + } + } + } +} +// + +// +public static class AsyncManualResetEventDemo +{ + public static async Task RunAsync() + { + var gate = new AsyncManualResetEvent(); + + Task waiter = Task.Run(async () => + { + Console.WriteLine("Waiter: waiting for signal..."); + await gate.WaitAsync(); + Console.WriteLine("Waiter: signal received!"); + }); + + await Task.Delay(100); + Console.WriteLine("Signaler: setting the event."); + gate.Set(); + + await waiter; + } +} +// + +// +// Educational only — use SemaphoreSlim(0, 1) instead of this sample implementation: call WaitAsync() to wait and Release() to signal. +public class AsyncAutoResetEvent +{ + private readonly Queue _waiters = new(); + private bool _signaled; + + public Task WaitAsync() + { + lock (_waiters) + { + if (_signaled) + { + _signaled = false; + return Task.CompletedTask; + } + else + { + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _waiters.Enqueue(tcs); + return tcs.Task; + } + } + } + + public void Set() + { + TaskCompletionSource? toRelease = null; + + lock (_waiters) + { + if (_waiters.Count > 0) + { + toRelease = _waiters.Dequeue(); + } + else if (!_signaled) + { + _signaled = true; + } + } + + toRelease?.TrySetResult(); + } +} +// + +// +public static class AsyncAutoResetEventDemo +{ + public static async Task RunAsync() + { + var autoEvent = new AsyncAutoResetEvent(); + + Task consumer = Task.Run(async () => + { + for (int i = 0; i < 3; i++) + { + await autoEvent.WaitAsync(); + Console.WriteLine($"Consumer: received signal {i + 1}"); + } + }); + + for (int i = 0; i < 3; i++) + { + await Task.Delay(50); + Console.WriteLine($"Producer: sending signal {i + 1}"); + autoEvent.Set(); + } + + await consumer; + } +} +// + +// +// Educational only — use Task.WhenAll() instead of this sample implementation to coordinate a fixed set of tasks. +public class AsyncCountdownEvent +{ + private readonly AsyncManualResetEvent _event = new(); + private int _count; + + public AsyncCountdownEvent(int initialCount) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(initialCount, nameof(initialCount)); + _count = initialCount; + } + + public Task WaitAsync() => _event.WaitAsync(); + + public void Signal() + { + if (_count <= 0) + throw new InvalidOperationException("The event is already signaled."); + + int newCount = Interlocked.Decrement(ref _count); + + if (newCount == 0) + _event.Set(); + else if (newCount < 0) + throw new InvalidOperationException("Too many signals."); + } + + public Task SignalAndWait() + { + Signal(); + return WaitAsync(); + } +} +// + +// +public static class AsyncCountdownEventDemo +{ + public static async Task RunAsync() + { + var countdown = new AsyncCountdownEvent(3); + + for (int i = 1; i <= 3; i++) + { + int id = i; + _ = Task.Run(async () => + { + await Task.Delay(id * 30); + Console.WriteLine($"Worker {id}: done."); + countdown.Signal(); + }); + } + + await countdown.WaitAsync(); + Console.WriteLine("All workers finished."); + } +} +// + +// +// Educational only — use Task.WhenAll() in a loop instead of this sample implementation, one call per round. +public class AsyncBarrier +{ + private readonly int _participantCount; + private int _remainingParticipants; + private TaskCompletionSource _tcs = new(TaskCreationOptions.RunContinuationsAsynchronously); + + public AsyncBarrier(int participantCount) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(participantCount, nameof(participantCount)); + _remainingParticipants = _participantCount = participantCount; + } + + public Task SignalAndWait() + { + TaskCompletionSource tcs = _tcs; + + if (Interlocked.Decrement(ref _remainingParticipants) == 0) + { + _remainingParticipants = _participantCount; + _tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + tcs.SetResult(); + } + + return tcs.Task; + } +} +// + +// +public static class AsyncBarrierDemo +{ + public static async Task RunAsync() + { + var barrier = new AsyncBarrier(3); + int rounds = 2; + + Task[] participants = Enumerable.Range(1, 3).Select(id => Task.Run(async () => + { + for (int round = 1; round <= rounds; round++) + { + Console.WriteLine($"Participant {id}: working on round {round}"); + await Task.Delay(id * 20); + Console.WriteLine($"Participant {id}: finished round {round}, waiting at barrier"); + await barrier.SignalAndWait(); + } + })).ToArray(); + + await Task.WhenAll(participants); + Console.WriteLine("All rounds complete."); + } +} +// + +public static class Program +{ + public static async Task Main() + { + Console.WriteLine("--- AsyncManualResetEvent ---"); + await AsyncManualResetEventDemo.RunAsync(); + + Console.WriteLine(); + Console.WriteLine("--- AsyncAutoResetEvent ---"); + await AsyncAutoResetEventDemo.RunAsync(); + + Console.WriteLine(); + Console.WriteLine("--- AsyncCountdownEvent ---"); + await AsyncCountdownEventDemo.RunAsync(); + + Console.WriteLine(); + Console.WriteLine("--- AsyncBarrier ---"); + await AsyncBarrierDemo.RunAsync(); + } +} diff --git a/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives/vb/AsyncCoordinationPrimitives.vbproj b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives/vb/AsyncCoordinationPrimitives.vbproj new file mode 100644 index 0000000000000..d91391285e5e7 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives/vb/AsyncCoordinationPrimitives.vbproj @@ -0,0 +1,9 @@ + + + + Exe + AsyncCoordinationPrimitives + net10.0 + + + diff --git a/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives/vb/Program.vb b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives/vb/Program.vb new file mode 100644 index 0000000000000..d4c61713f4e68 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/async-coordination-primitives/vb/Program.vb @@ -0,0 +1,231 @@ +Imports System.Threading + +' +' Educational only — use TaskCompletionSource(Of T) directly instead of this sample implementation; create a new instance each cycle. +Public Class AsyncManualResetEvent + Private _tcs As TaskCompletionSource = New TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously) + + Public Function WaitAsync() As Task + Return _tcs.Task + End Function + + Public Sub [Set]() + _tcs.TrySetResult() + End Sub + + Public Sub Reset() + Do + Dim tcs As TaskCompletionSource = _tcs + If Not tcs.Task.IsCompleted OrElse + Interlocked.CompareExchange( + _tcs, + New TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously), + tcs) Is tcs Then + Return + End If + Loop + End Sub +End Class +' + +' +Public Module AsyncManualResetEventDemo + Public Async Function RunAsync() As Task + Dim gate As New AsyncManualResetEvent() + + Dim waiter As Task = Task.Run(Async Function() + Console.WriteLine("Waiter: waiting for signal...") + Await gate.WaitAsync() + Console.WriteLine("Waiter: signal received!") + End Function) + + Await Task.Delay(100) + Console.WriteLine("Signaler: setting the event.") + gate.Set() + + Await waiter + End Function +End Module +' + +' +' Educational only — use SemaphoreSlim(0, 1) instead of this sample implementation: call WaitAsync() to wait and Release() to signal. +Public Class AsyncAutoResetEvent + Private ReadOnly _waiters As New Queue(Of TaskCompletionSource)() + Private _signaled As Boolean + + Public Function WaitAsync() As Task + SyncLock _waiters + If _signaled Then + _signaled = False + Return Task.CompletedTask + Else + Dim tcs As New TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously) + _waiters.Enqueue(tcs) + Return tcs.Task + End If + End SyncLock + End Function + + Public Sub [Set]() + Dim toRelease As TaskCompletionSource = Nothing + + SyncLock _waiters + If _waiters.Count > 0 Then + toRelease = _waiters.Dequeue() + ElseIf Not _signaled Then + _signaled = True + End If + End SyncLock + + toRelease?.TrySetResult() + End Sub +End Class +' + +' +Public Module AsyncAutoResetEventDemo + Public Async Function RunAsync() As Task + Dim autoEvent As New AsyncAutoResetEvent() + + Dim consumer As Task = Task.Run(Async Function() + For i As Integer = 0 To 2 + Await autoEvent.WaitAsync() + Console.WriteLine($"Consumer: received signal {i + 1}") + Next + End Function) + + For i As Integer = 0 To 2 + Await Task.Delay(50) + Console.WriteLine($"Producer: sending signal {i + 1}") + autoEvent.Set() + Next + + Await consumer + End Function +End Module +' + +' +' Educational only — use Task.WhenAll() instead of this sample implementation to coordinate a fixed set of tasks. +Public Class AsyncCountdownEvent + Private ReadOnly _event As New AsyncManualResetEvent() + Private _count As Integer + + Public Sub New(initialCount As Integer) + If initialCount <= 0 Then Throw New ArgumentOutOfRangeException(NameOf(initialCount)) + _count = initialCount + End Sub + + Public Function WaitAsync() As Task + Return _event.WaitAsync() + End Function + + Public Sub Signal() + If _count <= 0 Then + Throw New InvalidOperationException("The event is already signaled.") + End If + + Dim newCount As Integer = Interlocked.Decrement(_count) + + If newCount = 0 Then + _event.Set() + ElseIf newCount < 0 Then + Throw New InvalidOperationException("Too many signals.") + End If + End Sub + + Public Function SignalAndWait() As Task + Signal() + Return WaitAsync() + End Function +End Class +' + +' +Public Module AsyncCountdownEventDemo + Public Async Function RunAsync() As Task + Dim countdown As New AsyncCountdownEvent(3) + + For i As Integer = 1 To 3 + Dim id As Integer = i + Dim backgroundTask As Task = Task.Run(Async Function() + Await Task.Delay(id * 30) + Console.WriteLine($"Worker {id}: done.") + countdown.Signal() + End Function) + Next + + Await countdown.WaitAsync() + Console.WriteLine("All workers finished.") + End Function +End Module +' + +' +' Educational only — use Task.WhenAll() in a loop instead of this sample implementation, one call per round. +Public Class AsyncBarrier + Private ReadOnly _participantCount As Integer + Private _remainingParticipants As Integer + Private _tcs As TaskCompletionSource = New TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously) + + Public Sub New(participantCount As Integer) + If participantCount <= 0 Then Throw New ArgumentOutOfRangeException(NameOf(participantCount)) + _participantCount = participantCount + _remainingParticipants = participantCount + End Sub + + Public Function SignalAndWait() As Task + Dim tcs As TaskCompletionSource = _tcs + + If Interlocked.Decrement(_remainingParticipants) = 0 Then + _remainingParticipants = _participantCount + _tcs = New TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously) + tcs.SetResult() + End If + + Return tcs.Task + End Function +End Class +' + +' +Public Module AsyncBarrierDemo + Public Async Function RunAsync() As Task + Dim barrier As New AsyncBarrier(3) + Dim rounds As Integer = 2 + + Dim participants As Task() = Enumerable.Range(1, 3).Select( + Function(id) Task.Run(Async Function() + For round As Integer = 1 To rounds + Console.WriteLine($"Participant {id}: working on round {round}") + Await Task.Delay(id * 20) + Console.WriteLine($"Participant {id}: finished round {round}, waiting at barrier") + Await barrier.SignalAndWait() + Next + End Function)).ToArray() + + Await Task.WhenAll(participants) + Console.WriteLine("All rounds complete.") + End Function +End Module +' + +Module Program + Sub Main() + Console.WriteLine("--- AsyncManualResetEvent ---") + AsyncManualResetEventDemo.RunAsync().Wait() + + Console.WriteLine() + Console.WriteLine("--- AsyncAutoResetEvent ---") + AsyncAutoResetEventDemo.RunAsync().Wait() + + Console.WriteLine() + Console.WriteLine("--- AsyncCountdownEvent ---") + AsyncCountdownEventDemo.RunAsync().Wait() + + Console.WriteLine() + Console.WriteLine("--- AsyncBarrier ---") + AsyncBarrierDemo.RunAsync().Wait() + End Sub +End Module diff --git a/docs/standard/io/how-to-use-named-pipes-for-network-interprocess-communication.md b/docs/standard/io/how-to-use-named-pipes-for-network-interprocess-communication.md index f360288c50e75..f1e3d33afff30 100644 --- a/docs/standard/io/how-to-use-named-pipes-for-network-interprocess-communication.md +++ b/docs/standard/io/how-to-use-named-pipes-for-network-interprocess-communication.md @@ -21,6 +21,9 @@ Named pipes provide interprocess communication between a pipe server and one or > [!IMPORTANT] > .NET on Linux uses Unix Domain Sockets (UDS) for the implementation of these APIs. +> [!NOTE] +> Starting in .NET 11, when you create a with on Unix, the underlying socket file is set to mode `0600` (owner read/write only) at bind time. Previously, the socket file inherited permissions from the process umask. For more information, see [NamedPipeServerStream with PipeOptions.CurrentUserOnly tightens Unix socket file permissions](../../core/compatibility/core-libraries/11/namedpipeserverstream-unix-permissions.md). + To implement name pipes, use the and classes. ## Example 1 diff --git a/docs/standard/library-guidance/cross-platform-targeting.md b/docs/standard/library-guidance/cross-platform-targeting.md index b2f7b750423c0..83032c9180f40 100644 --- a/docs/standard/library-guidance/cross-platform-targeting.md +++ b/docs/standard/library-guidance/cross-platform-targeting.md @@ -1,7 +1,7 @@ --- title: Cross-platform targeting for .NET libraries description: Best practice recommendations for creating cross-platform .NET libraries. -ms.date: 11/06/2025 +ms.date: 04/13/2026 --- # Cross-platform targeting @@ -24,12 +24,10 @@ These goals don't always require the same approach. If your target applications For more information about how .NET compares to .NET Standard, see [.NET 5 and .NET Standard](../net-standard.md#net-5-and-net-standard). -![.NET Standard](./media/cross-platform-targeting/platforms-netstandard.png) - If your project targets .NET or .NET Standard and compiles successfully, it doesn't guarantee that the library will run successfully on all platforms: - Platform-specific APIs will fail on other platforms. For example, will succeed on Windows and throw when used on any other OS. -- APIs can behave differently. For example, reflection APIs have different performance characteristics when an application uses ahead-of-time compilation on iOS or UWP. +- APIs can behave differently. For example, reflection APIs have different performance characteristics when an application uses ahead-of-time compilation on iOS or Android. > [!TIP] > The .NET team offers a [Platform compatibility analyzer](../analyzers/platform-compat-analyzer.md) to help you discover possible issues. @@ -52,7 +50,7 @@ If your project targets .NET or .NET Standard and compiles successfully, it does ❌ DO NOT include a .NET Standard target if the library relies on a platform-specific app model. -> For example, a UWP control toolkit library depends on an app model that is only available on UWP. App model specific APIs aren't available in .NET Standard. +> For example, a WinUI control toolkit library depends on an app model that is only available on Windows. App model specific APIs aren't available in .NET Standard. ❌ DO NOT publish for `netstandard2.0` if your project or dependencies multi-target. @@ -62,9 +60,9 @@ If your project targets .NET or .NET Standard and compiles successfully, it does Sometimes you need to access framework-specific APIs from your libraries. The best way to call framework-specific APIs is to use multi-targeting, which builds your project for many [.NET target frameworks](../frameworks.md) rather than for just one. -To shield your consumers from having to build for individual frameworks, you should strive to have a .NET Standard output plus one or more framework-specific outputs. With you multi-target, all assemblies are packaged inside a single NuGet package. Consumers can then reference the same package and NuGet will pick the appropriate implementation. Your .NET Standard library serves as the fallback library that is used everywhere, except for the cases where your NuGet package offers a framework-specific implementation. Multi-targeting allows you to use conditional compilation in your code and call framework-specific APIs. +To shield your consumers from having to build for individual frameworks, you should strive to have a .NET Standard output plus one or more framework-specific outputs. With multi-targeting, all assemblies are packaged inside a single NuGet package. Consumers can then reference the same package and NuGet picks the appropriate implementation. Your .NET Standard library serves as the fallback library that is used everywhere, except for the cases where your NuGet package offers a framework-specific implementation. Multi-targeting allows you to use conditional compilation in your code and call framework-specific APIs. -![NuGet package with multiple assemblies](./media/cross-platform-targeting/nuget-package-multiple-assemblies.png) +For example, a NuGet package that multi-targets `netstandard2.0` and `net8.0` contains separate assemblies for each target. NuGet selects the best-matching assembly for each consumer: .NET 8 applications use the `net8.0` assembly, while .NET Framework and older .NET applications fall back to the `netstandard2.0` assembly. ✔️ CONSIDER targeting .NET implementations in addition to .NET Standard. @@ -80,8 +78,8 @@ public static class GpsLocation { #if NET462 return CallDotNetFrameworkApi(); -#elif WINDOWS_UWP - return CallUwpApi(); +#elif NET8_0_WINDOWS + return CallWindowsApi(); #else throw new PlatformNotSupportedException(); #endif @@ -93,7 +91,7 @@ public static class GpsLocation { get { -#if NET462 || WINDOWS_UWP +#if NET462 || NET8_0_WINDOWS return true; #else return false; diff --git a/docs/standard/library-guidance/media/cross-platform-targeting/nuget-package-multiple-assemblies.png b/docs/standard/library-guidance/media/cross-platform-targeting/nuget-package-multiple-assemblies.png deleted file mode 100644 index ea0497db2ae13..0000000000000 Binary files a/docs/standard/library-guidance/media/cross-platform-targeting/nuget-package-multiple-assemblies.png and /dev/null differ diff --git a/docs/standard/library-guidance/media/cross-platform-targeting/platforms-netstandard.png b/docs/standard/library-guidance/media/cross-platform-targeting/platforms-netstandard.png deleted file mode 100644 index 9c437bca91bad..0000000000000 Binary files a/docs/standard/library-guidance/media/cross-platform-targeting/platforms-netstandard.png and /dev/null differ