From 030efe578cf7ae89257a50ed7150ac1e8b9a6e45 Mon Sep 17 00:00:00 2001 From: "azure-sdk-automation[bot]" <191533747+azure-sdk-automation[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:32:51 -0700 Subject: [PATCH 1/9] Update package index with latest published versions (#55615) Co-authored-by: azure-sdk --- docs/azure/includes/dotnet-all.md | 2 +- docs/azure/includes/dotnet-new.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/azure/includes/dotnet-all.md b/docs/azure/includes/dotnet-all.md index e6574e4855c21..0185a29012f57 100644 --- a/docs/azure/includes/dotnet-all.md +++ b/docs/azure/includes/dotnet-all.md @@ -149,7 +149,7 @@ | Functions extension for Blob Storage | NuGet [5.3.8](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/5.3.8) | [docs](/dotnet/api/overview/azure/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs-readme) | GitHub [5.3.8](https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs_5.3.8/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/) | | Functions extension for Storage Queues | NuGet [5.3.8](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.Storage.Queues/5.3.8) | [docs](/dotnet/api/overview/azure/Microsoft.Azure.WebJobs.Extensions.Storage.Queues-readme) | GitHub [5.3.8](https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.Azure.WebJobs.Extensions.Storage.Queues_5.3.8/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Queues/) | | Functions extension for WebPubSub for SocketIO | NuGet [1.0.0](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO/1.0.0) | [docs](/dotnet/api/overview/azure/Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO-readme) | GitHub [1.0.0](https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO_1.0.0/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO/) | -| Provisioning | NuGet [1.5.0](https://www.nuget.org/packages/Azure.Provisioning/1.5.0)
NuGet [1.6.0-beta.1](https://www.nuget.org/packages/Azure.Provisioning/1.6.0-beta.1) | [docs](/dotnet/api/overview/azure/Provisioning-readme) | GitHub [1.5.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning_1.5.0/sdk/provisioning/Azure.Provisioning/)
GitHub [1.6.0-beta.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning_1.6.0-beta.1/sdk/provisioning/Azure.Provisioning/) | +| Provisioning | NuGet [1.6.0](https://www.nuget.org/packages/Azure.Provisioning/1.6.0) | [docs](/dotnet/api/overview/azure/Provisioning-readme) | GitHub [1.6.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning_1.6.0/sdk/provisioning/Azure.Provisioning/) | | Provisioning - API Management | NuGet [1.0.0-beta.1](https://www.nuget.org/packages/Azure.Provisioning.ApiManagement/1.0.0-beta.1) | [docs](/dotnet/api/overview/azure/Provisioning.ApiManagement-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning.ApiManagement_1.0.0-beta.1/sdk/apimanagement/Azure.Provisioning.ApiManagement/) | | Provisioning - App Configuration | NuGet [1.1.0](https://www.nuget.org/packages/Azure.Provisioning.AppConfiguration/1.1.0)
NuGet [1.2.0-beta.1](https://www.nuget.org/packages/Azure.Provisioning.AppConfiguration/1.2.0-beta.1) | [docs](/dotnet/api/overview/azure/Provisioning.AppConfiguration-readme) | GitHub [1.1.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning.AppConfiguration_1.1.0/sdk/provisioning/Azure.Provisioning.AppConfiguration/)
GitHub [1.2.0-beta.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning.AppConfiguration_1.2.0-beta.1/sdk/provisioning/Azure.Provisioning.AppConfiguration/) | | Provisioning - App Service | NuGet [1.3.1](https://www.nuget.org/packages/Azure.Provisioning.AppService/1.3.1)
NuGet [1.4.0-beta.2](https://www.nuget.org/packages/Azure.Provisioning.AppService/1.4.0-beta.2) | [docs](/dotnet/api/overview/azure/Provisioning.AppService-readme) | GitHub [1.3.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning.AppService_1.3.1/sdk/provisioning/Azure.Provisioning.AppService/)
GitHub [1.4.0-beta.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning.AppService_1.4.0-beta.2/sdk/provisioning/Azure.Provisioning.AppService/) | diff --git a/docs/azure/includes/dotnet-new.md b/docs/azure/includes/dotnet-new.md index fe18d5b13f820..28612ad0b9ef8 100644 --- a/docs/azure/includes/dotnet-new.md +++ b/docs/azure/includes/dotnet-new.md @@ -162,7 +162,7 @@ | Functions extension for Blob Storage | NuGet [5.3.8](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/5.3.8) | [docs](/dotnet/api/overview/azure/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs-readme) | GitHub [5.3.8](https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs_5.3.8/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Blobs/) | | Functions extension for Storage Queues | NuGet [5.3.8](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.Storage.Queues/5.3.8) | [docs](/dotnet/api/overview/azure/Microsoft.Azure.WebJobs.Extensions.Storage.Queues-readme) | GitHub [5.3.8](https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.Azure.WebJobs.Extensions.Storage.Queues_5.3.8/sdk/storage/Microsoft.Azure.WebJobs.Extensions.Storage.Queues/) | | Functions extension for WebPubSub for SocketIO | NuGet [1.0.0](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO/1.0.0) | [docs](/dotnet/api/overview/azure/Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO-readme) | GitHub [1.0.0](https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO_1.0.0/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSubForSocketIO/) | -| Provisioning | NuGet [1.5.0](https://www.nuget.org/packages/Azure.Provisioning/1.5.0)
NuGet [1.6.0-beta.1](https://www.nuget.org/packages/Azure.Provisioning/1.6.0-beta.1) | [docs](/dotnet/api/overview/azure/Provisioning-readme) | GitHub [1.5.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning_1.5.0/sdk/provisioning/Azure.Provisioning/)
GitHub [1.6.0-beta.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning_1.6.0-beta.1/sdk/provisioning/Azure.Provisioning/) | +| Provisioning | NuGet [1.6.0](https://www.nuget.org/packages/Azure.Provisioning/1.6.0) | [docs](/dotnet/api/overview/azure/Provisioning-readme) | GitHub [1.6.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning_1.6.0/sdk/provisioning/Azure.Provisioning/) | | Provisioning - API Management | NuGet [1.0.0-beta.1](https://www.nuget.org/packages/Azure.Provisioning.ApiManagement/1.0.0-beta.1) | [docs](/dotnet/api/overview/azure/Provisioning.ApiManagement-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning.ApiManagement_1.0.0-beta.1/sdk/apimanagement/Azure.Provisioning.ApiManagement/) | | Provisioning - App Configuration | NuGet [1.1.0](https://www.nuget.org/packages/Azure.Provisioning.AppConfiguration/1.1.0)
NuGet [1.2.0-beta.1](https://www.nuget.org/packages/Azure.Provisioning.AppConfiguration/1.2.0-beta.1) | [docs](/dotnet/api/overview/azure/Provisioning.AppConfiguration-readme) | GitHub [1.1.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning.AppConfiguration_1.1.0/sdk/provisioning/Azure.Provisioning.AppConfiguration/)
GitHub [1.2.0-beta.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning.AppConfiguration_1.2.0-beta.1/sdk/provisioning/Azure.Provisioning.AppConfiguration/) | | Provisioning - App Service | NuGet [1.3.1](https://www.nuget.org/packages/Azure.Provisioning.AppService/1.3.1)
NuGet [1.4.0-beta.2](https://www.nuget.org/packages/Azure.Provisioning.AppService/1.4.0-beta.2) | [docs](/dotnet/api/overview/azure/Provisioning.AppService-readme) | GitHub [1.3.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning.AppService_1.3.1/sdk/provisioning/Azure.Provisioning.AppService/)
GitHub [1.4.0-beta.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Provisioning.AppService_1.4.0-beta.2/sdk/provisioning/Azure.Provisioning.AppService/) | From 568ecd8d82250ce96e06b8719acb1c6ad217e674 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 20 Aug 2026 16:46:14 -0400 Subject: [PATCH 2/9] [Everday C#] Add Expressions: operators article (#55469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Expressions: operators article (issue #55334) Create docs/csharp/fundamentals/expressions/operators.md covering arithmetic, unary, increment/decrement, relational, equality survey, conditional-logical, conditional (?:), simple and compound assignment. - Add operators snippets project (net10.0, nullable, implicit usings) with 9 region-marked examples; 0 warnings, 0 errors - Add operators.md TOC node under Expressions and statements - Add reciprocal link in expressions/index.md - Add reciprocal link in expressions/equality.md - Link excluded operators (shift/bitwise, checked/unchecked) to existing Language Reference pages Closes #55334 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f5acfb15-adff-4860-a307-213196efda1c * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * 2nd draft - Add negative integer division example (-7/2 = -3) to clarify truncation toward zero - Add negative-operand remainder examples (-7%3 = -1, 7%-3 = 1) with sign rule explanation - Explain char relational comparison uses Unicode code point values (tied to grade example) - Update != comment to explicitly say 'true when values are not equal' - Remove invalid commented-out code from EqualityOps snippet; move === example to NOTE callout in article prose as fenced code block - Remove nested conditional example and related prose from ConditionalOp section - Add Console.WriteLine after each compound assignment step (hp progression: 100 -> 120 -> 110 -> 220 -> 73 -> 3) - Rename 'Operators not covered here' to 'Other C# operators'; expand bullets with concise definitions - Add displayName to toc.yml entry for operators.md with all covered operators Copilot-Session: f5acfb15-adff-4860-a307-213196efda1c Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> * A few small repairs Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> * Retire Programming Guide equality articles; migrate to Fundamentals (#55334) Retire the three Programming Guide equality articles and preserve their unique content in docs/csharp/fundamentals/expressions/equality.md: - equality-comparisons.md - how-to-test-for-reference-equality-identity.md - how-to-define-value-equality-for-a-type.md Content migrated into equality.md: - Equivalence contract (5 rules: reflexive, symmetric, transitive, consistent, null behavior) added to the manual-implementation section. - New section: 'Records with reference-type members' — explains that synthesized record equality uses each member's own equality semantics, so List/array members compare by reference; shows custom IEquatable override with SequenceEqual as the recommended fix. - New section: 'Polymorphic equality in unsealed class hierarchies' — explains the compile-time dispatch hazard with IEquatable, the GetType() guard and virtual Equals pattern for correct unsealed-class equality, and notes that sealed classes and records avoid the problem. Snippet additions to snippets/equality/Program.cs: - RecordWithCollectionProblem / RecordWithCollectionFixed regions - PlaylistFixedDefinition type (custom IEquatable record) - PolymorphicEqualityDefinition (Shape/Circle hierarchy with GetType() guard) - PolymorphicEqualityUsage region Intentionally omitted: string-interning note (per Bill's explicit decision). Retirement wiring: - 3 redirects added to .openpublishing.redirection.csharp.json - TOC entries and empty parent node removed from toc.yml - All 5 inbound links updated: objects.md, how-to/index.md, overloaded-operator-errors.md, record-declaration-errors.md, equality-operators.md - Orphaned snippet projects deleted Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f5acfb15-adff-4860-a307-213196efda1c * restructure equality article. Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> * Add 'Equality in class hierarchies' section to Language Reference operators. Implements the persisted plan from PR #55469 equality restructuring: - Add ## Equality in class hierarchies before ## Operator overloadability in equality-operators.md; covers declared/runtime-type dispatch hazards, GetType() guard, virtual Equals, derived-class augmentation, GetHashCode with GetType(), sealed-class simplification, and records guidance. - Create net10.0 snippet project at docs/csharp/language-reference/operators/snippets/EqualityHierarchies/ with HierarchyShapeDefinition, HierarchyCircleDefinition, HierarchyUsage regions (all build-verified, 0 warnings/errors). - Update metadata: description, ms.date, helpviewer_keywords. - Add reciprocal Fundamentals link in new section. Cray item 3 (relocate polymorphic equality to Language Reference) implemented. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f5acfb15-adff-4860-a307-213196efda1c * Simplify Fundamentals equality article; add byte-range note to operators. Implements remaining Cray review items for PR #55469: Cray item 1 (Records with reference-type members): - Remove PlaylistFixed/IEquatable implementation code; replace rendered code blocks with brief named-strategy list; keep RecordWithCollectionProblem surprise example intact. Cray item 2 (Implement equality yourself): - Lead with code (ColorDefinition first); shorten IMPORTANT callout to 2 sentences; consolidate member-list description into commentary after the code; compact equivalence contract intro; demote IEquatable footnote. - Retain IEquatableUsage region to show identity-vs-value contrast. Cray item 3 (Polymorphic section bridge): - Replace full polymorphic implementation in Fundamentals with a 3-sentence hazard summary + link to Language Reference ## Equality in class hierarchies. - Preserve ## Polymorphic equality in unsealed class hierarchies heading. Cray item 4 (operators.md byte-range): - Add byte-range clarification: 'the result, 210, fits within the byte range of 0-255'; beginner-safe, no checked/unchecked discussion. Cray item 5 (hash-loop): auto-resolved by PlaylistFixed removal. Snippets: - Remove PlaylistFixedDefinition, RecordWithCollectionFixed regions and PlaylistFixed type from Fundamentals Program.cs. - Remove PolymorphicEqualityDefinition, PolymorphicEqualityUsage regions (Shape/Circle now live in LR EqualityHierarchies project). - Fundamentals snippet builds 0 warnings/errors (net10.0). Links/redirects: - All three Programming Guide redirect targets unchanged (anchors verified). - Add reciprocal LR link in equality.md See also. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f5acfb15-adff-4860-a307-213196efda1c * Polish: improve equality documentation formatting and clarity - Add blank line before 'Polymorphic equality in unsealed class hierarchies' section heading in equality.md - Clarify 'declared type' with parenthetical '(the type written in the variable declaration)' in equality-operators.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f5acfb15-adff-4860-a307-213196efda1c * Final review Do a final review pass of all the changed content. * Fix build warnings. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Copilot-Session: f5acfb15-adff-4860-a307-213196efda1c --- .openpublishing.redirection.csharp.json | 12 + .../fundamentals/expressions/equality.md | 92 +++---- docs/csharp/fundamentals/expressions/index.md | 1 + .../fundamentals/expressions/operators.md | 163 ++++++++++++ .../expressions/snippets/equality/Program.cs | 41 +-- .../expressions/snippets/operators/Program.cs | 155 +++++++++++ .../snippets/operators/operators.csproj} | 4 +- .../fundamentals/object-oriented/objects.md | 32 +-- docs/csharp/how-to/index.md | 4 +- .../overloaded-operator-errors.md | 10 +- .../record-declaration-errors.md | 6 +- .../operators/equality-operators.md | 70 ++++- .../EqualityHierarchies.csproj} | 4 +- .../snippets/EqualityHierarchies/Program.cs | 82 ++++++ .../equality-comparisons.md | 53 ---- ...how-to-define-value-equality-for-a-type.md | 214 --------------- ...to-test-for-reference-equality-identity.md | 32 --- .../RecordCollectionsIssue/Program.cs | 144 ---------- .../ValueEqualityClass/Program.cs | 175 ------------- .../ValueEqualityClass.csproj | 10 - .../ValueEqualityPolymorphic/Program.cs | 247 ------------------ .../ValueEqualityPolymorphic.csproj | 10 - .../ValueEqualityRecord/Program.cs | 99 ------- .../ValueEqualityRecord.csproj | 10 - .../ValueEqualityStruct/Program.cs | 97 ------- .../Program.cs | 103 -------- .../TestingReferenceEquality.csproj | 11 - docs/csharp/toc.yml | 11 +- 28 files changed, 570 insertions(+), 1322 deletions(-) create mode 100644 docs/csharp/fundamentals/expressions/operators.md create mode 100644 docs/csharp/fundamentals/expressions/snippets/operators/Program.cs rename docs/csharp/{programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityStruct/ValueEqualityStruct.csproj => fundamentals/expressions/snippets/operators/operators.csproj} (80%) rename docs/csharp/{programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/RecordCollectionsIssue.csproj => language-reference/operators/snippets/EqualityHierarchies/EqualityHierarchies.csproj} (80%) create mode 100644 docs/csharp/language-reference/operators/snippets/EqualityHierarchies/Program.cs delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/equality-comparisons.md delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/how-to-test-for-reference-equality-identity.md delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/Program.cs delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityClass/Program.cs delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityClass/ValueEqualityClass.csproj delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityPolymorphic/Program.cs delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityPolymorphic/ValueEqualityPolymorphic.csproj delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityRecord/Program.cs delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityRecord/ValueEqualityRecord.csproj delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityStruct/Program.cs delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-test-for-reference-equality-identity/Program.cs delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-test-for-reference-equality-identity/TestingReferenceEquality.csproj diff --git a/.openpublishing.redirection.csharp.json b/.openpublishing.redirection.csharp.json index 2275630efad74..213055a1d48e7 100644 --- a/.openpublishing.redirection.csharp.json +++ b/.openpublishing.redirection.csharp.json @@ -5828,6 +5828,18 @@ { "source_path_from_root": "/redirections/proposals/csharp-9.0/nullable-reference-types-specification.md", "redirect_url": "/dotnet/csharp/language-reference/language-specification/types#893-nullable-reference-types" + }, + { + "source_path_from_root": "/docs/csharp/programming-guide/statements-expressions-operators/equality-comparisons.md", + "redirect_url": "/dotnet/csharp/fundamentals/expressions/equality" + }, + { + "source_path_from_root": "/docs/csharp/programming-guide/statements-expressions-operators/how-to-test-for-reference-equality-identity.md", + "redirect_url": "/dotnet/csharp/fundamentals/expressions/equality#use-objectreferenceequals-to-test-identity-directly" + }, + { + "source_path_from_root": "/docs/csharp/programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md", + "redirect_url": "/dotnet/csharp/fundamentals/expressions/equality#implement-equality-yourself-when-a-type-cant-be-a-record" } ] } diff --git a/docs/csharp/fundamentals/expressions/equality.md b/docs/csharp/fundamentals/expressions/equality.md index efbb6325469d7..76914fba02875 100644 --- a/docs/csharp/fundamentals/expressions/equality.md +++ b/docs/csharp/fundamentals/expressions/equality.md @@ -1,9 +1,18 @@ --- title: "C# Equality comparisons" -description: Learn how C# compares values and references with ==, !=, Equals, GetHashCode, and ReferenceEquals for classes, structs, records, and tuples. -ms.date: 07/22/2026 +description: Learn how C# compares values and references with ==, !=, Equals, GetHashCode, and ReferenceEquals for classes, structs, records, and tuples. Covers the equivalence contract, polymorphic equality in class hierarchies, and records with collection members. +ms.date: 08/18/2026 ms.topic: concept-article ai-usage: ai-assisted +helpviewer_keywords: + - "object equality [C#]" + - "value equality [C#]" + - "reference equality [C#]" + - "object identity [C#]" + - "object equivalence [C#]" + - "overriding Equals method [C#]" + - "Equals method [C#], overriding" + - "equivalence [C#]" --- # C# Equality comparisons @@ -11,13 +20,13 @@ ai-usage: ai-assisted > [!TIP] > This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. > -> **Coming from another language?** In Java, `==` on objects and JavaScript `===` on objects test identity, not content. C# classes work the same way by default. In Python, `==` calls `__eq__` and tests content by default , similar to how C# [records](../types/records.md) compare. C# [structs](../types/structs.md) also compare by value when you call `Equals`. +> **Coming from another language?** In Java, `==` on objects and JavaScript `===` on objects test identity, not content. C# classes work the same way by default. In Python, `==` calls `__eq__` and tests content by default, similar to how C# [records](../types/records.md) compare. C# [structs](../types/structs.md) also compare by value when you call `Equals`. -C# distinguishes two kinds of equality. *Value equality* means two instances are equal when their data matches. *Reference equality* means two variables are equal only when they point to the same object in memory. This condition is also called *identity*. The kind of type gives you the best first clue about the default equality behavior: value types usually compare data, and reference types usually compare identity. Defaults aren't destiny, but that mental model prevents subtle bugs where two objects that look identical aren't considered equal, or where a mutation through one variable silently changes what another variable sees. +C# distinguishes two kinds of equality. *Value equality* means two instances are equal when their data matches. *Reference equality* means two variables are equal only when they point to the same object in memory. This condition is also called *identity*. Value types usually compare data, and reference types usually compare identity. Type authors can change those defaults, but that mental model prevents subtle bugs where two objects that look identical aren't considered equal, or where a mutation through one variable silently changes what another variable sees. ## Value types, reference types, and equality defaults -Every type in C# is either a *value type* or a *reference type*. A *value type* holds its data directly in the variable. A *reference type* holds a reference to an object. When you assign a reference-type variable to another variable, both variables refer to the same object. This article uses that distinction as a quick refresher. For more information about value types and reference types, see [Type system overview](../types/index.md#value-types-and-reference-types). +Every type in C# is either a *value type* or a *reference type*. A *value type* holds its data directly in the variable. A *reference type* holds a reference to an object. When you assign a reference-type variable to another variable, both variables refer to the same object. For more information about value types and reference types, see [Type system overview](../types/index.md#value-types-and-reference-types). The default equality behavior usually follows the kind of type: @@ -26,11 +35,11 @@ The default equality behavior usually follows the kind of type: - **[Tuples](../types/tuples.md)** are value types. Two tuples are equal when all their element values match. - **[Classes](../types/classes.md)** are reference types. A plain class uses reference equality, so `==` and test whether two variables point to the same object. -A plain class shows reference equality. Two separate objects with the same data aren't equal, but two variables that refer to the same object are equal: +A class uses reference equality. Two separate objects with the same data aren't equal, but two variables that refer to the same object are equal: :::code language="csharp" source="snippets/equality/Program.cs" ID="ClassEquality"::: -A plain `struct` shows value equality through . Two struct instances are equal when their fields match: +A `struct` shows value equality through . Two struct instances are equal when their fields match: :::code language="csharp" source="snippets/equality/Program.cs" ID="StructEquality"::: @@ -42,19 +51,28 @@ Tuples are value types too. Two tuples are equal when every element value matche For more information about tuple syntax and deconstruction, see [Tuples and deconstruction](../types/tuples.md). -## Types can define different equality semantics +## Use `Object.ReferenceEquals` to test identity directly + + always tests identity regardless of how a type overrides or overloads `==`. Use it as an identity diagnostic when you need to confirm whether two variables point to the exact same object: + +:::code language="csharp" source="snippets/equality/Program.cs" ID="ReferenceEqualsDemo"::: + +A common use is inside an `Equals` override to short-circuit the full comparison: when both arguments are the same reference, they're always equal without checking individual fields. -Defaults aren't destiny. Some types define equality semantics that differ from the type-kind default, and your own types can do the same when their data should determine equality. +> [!NOTE] +> When variables are typed as an [interface](../types/interfaces.md), `==` checks whether the interface variables refer to the same object. A call to `Equals` still runs the underlying object's implementation. -Common exceptions and customizations include: +> [!NOTE] +> always returns `false` when comparing value types, even if both arguments contain the same values. This behavior occurs because each value-type argument is independently *boxed* into a separate heap object when passed to `ReferenceEquals`. -- **[Records](../types/records.md)** generate value equality and include `==`/`!=` operators. The next section shows how the `record` modifier gives value equality to both record classes and record structs. -- **Strings** are classes, but `==` and compare string content, not identity. -- **Your own classes and structs** can define value equality when their data should determine equality. +## Types can define different equality semantics -Equality is woven through these related members: +Types *can* define equality semantics that differ from the default behavior. The most common reason is to implement value equality. If you create a type that represents data, such as a bank account, a product in inventory, or a user in a system, consider instances with the same values as equal. *Choose [record types](../types/records.md) for implementing value equality*, and the compiler generates all the necessary equality members for you. -- `==`: the equality operator. Most types use this as the primary equality check. Its behavior depends on whether the type has a built-in or user-defined `==` operator. +> [!NOTE] +> **Strings** are classes, but `==` and compare string content, not identity. + +- `==`: the equality operator. Most types use this operator as the primary equality check. Its behavior depends on whether the type has a built-in or user-defined `==` operator. - `!=`: the inequality operator. When a type defines a user-defined `==` operator, it must also define `!=`. - : a virtual method inherited by every type. You can override it to change equality semantics for a type. - : a virtual method used by hash-based collections. When two values are equal, their hash codes must also be equal. @@ -76,40 +94,22 @@ The same compiler generation applies to `record struct` types: Record types generate the whole equality set for their own type. Both `record class` and `record struct` types override and . They also generate `==` and `!=` operators, plus a typed `Equals` method for the record type. Unlike a plain `struct`, a `record struct` therefore supports `==` and `!=` automatically. For more information about record types and their equality semantics, see [Records](../types/records.md#value-equality). -## Implement equality yourself when a type can't be a record - -> [!IMPORTANT] -> This section shows how to implement by hand the equality behavior that the compiler generates when you add `record` to a type. If your type can be a record, use `record` instead. It generates all these members for you. Implement them manually only when your type can't be a record. - -When a class or struct represents a value, such as a color or a measurement, the equality members for that type must agree. The easiest way to achieve this consistency is to declare the type as a `record`. If the type can't be a record, such as when it must derive from a non-record class, implement the equality members yourself. The language enforces that user-defined `==` and `!=` operators must be declared as a pair. If you provide those operators, compiler warning [CS0660](../../language-reference/compiler-messages/overloaded-operator-errors.md#equality-operators) means the type also needs an override. Warning [CS0661](../../language-reference/compiler-messages/overloaded-operator-errors.md#equality-operators) means the type also needs an override. - -In a complete manual implementation, provide these members: - -- `==` and `!=` operators. Add them as a pair because the compiler requires a type that overloads one to overload the other. -- An `override` of . This override changes equality semantics for the type and keeps object-level equality consistent. -- An `override` of . Objects that are equal must return the same hash code. Without this pairing, the type behaves incorrectly in hash-based collections such as `Dictionary` or `HashSet`. See for guidance on a correct implementation. -- Optionally, a typed `Equals` method by implementing . You often see this written as `Equals(T?)` in docs: `T` is a [type parameter](../types/generics.md), a placeholder for the current type, and `?` is a [nullable annotation](../null-safety/index.md) that says the argument can be `null`. This typed method can avoid extra conversions when callers already have the same type, but it's a secondary optimization. - -The following example starts with the and overrides, plus the optional typed `Equals` member, so you can see their effect before the `==` and `!=` operators are added. `HashCode.Combine` is a library helper that builds one hash code from the same values used by `Equals`: - -:::code language="csharp" source="snippets/equality/Program.cs" ID="ColorDefinition"::: - -At this point, `Equals` reflects value equality, but `==` still tests identity for the class because the type hasn't declared `==` and `!=` operators. Plain structs likewise still don't have a predefined `==` operator unless you declare one: +### Records with reference-type members -:::code language="csharp" source="snippets/equality/Program.cs" ID="IEquatableUsage"::: +Record equality uses the members' own equality semantics. Each property or field is compared by using its own `Equals` method. For most scalar values, such as `int`, `string`, or `DateTime`, this approach compares the values of the record members. The subtlety arises with common mutable collections such as `List` or `T[]`: these types compare by reference, so two record instances that contain *different list objects with the same content* are **not** considered equal by the synthesized record equality. -Adding `==` and `!=` operators is the remaining step when you need operator comparisons. This article intentionally stops before the full operator implementation so the first pass can focus on the equality contract. The operator-focused follow-up shows the completed shape. For the operator syntax, see [Equality operators](../../language-reference/operators/equality-operators.md) in the language reference. +:::code language="csharp" source="snippets/equality/Program.cs" ID="RecordWithCollectionProblem"::: -## Use `Object.ReferenceEquals` to test identity directly +`playlist1` and `playlist2` are separate `List` instances. Even though their contents match, `Equals` returns `false`. - always tests identity regardless of how a type overrides or overloads `==`. Use it as an identity diagnostic when you need to confirm whether two variables point to the exact same object: +When you need record equality to reflect collection *contents*, you have a few options: -:::code language="csharp" source="snippets/equality/Program.cs" ID="ReferenceEqualsDemo"::: +- **Implement `IEquatable`** on the record and override `Equals` to use for the collection members. +- **Use a collection type with value equality** — for example, a custom `IEqualityComparer` or a type whose own `Equals` compares elements. +- **Design around identity**: if the record represents an entity rather than a pure value, reference equality for its collection members might be intentional. -A common use is inside an `Equals` override to short-circuit the full comparison: when both arguments are the same reference, they're always equal without checking individual fields. - -> [!NOTE] -> Advanced detail: when variables are typed as an [interface](../types/interfaces.md), `==` checks whether the interface variables refer to the same object. A call to `Equals` still runs the underlying object's implementation. +> [!IMPORTANT] +> Manual implementation of equality is rare today in C#. Records handle the common scenario of value equality automatically. If you need to implement equality manually - for example, because your type must derive from a non-record base class - see [Implement equality yourself when a type can't be a record](../../language-reference/operators/equality-operators.md#implement-equality-yourself-when-a-type-cant-be-a-record) in the language reference. ## See also @@ -117,5 +117,7 @@ A common use is inside an `Equals` override to short-circuit the full comparison - [Classes](../types/classes.md) - [Structs](../types/structs.md) - [Records](../types/records.md) -- [Tuples and deconstruction](../types/tuples.md) -- [Equality operators (language reference)](../../language-reference/operators/equality-operators.md) +- [Tuples and deconstruction](../types/tuples.md). +- [Equality operators (language reference)](../../language-reference/operators/equality-operators.md). +- [Equality in class hierarchies](../../language-reference/operators/equality-operators.md#equality-in-class-hierarchies) — advanced guidance on polymorphic equality. +- [Arithmetic, comparison, logical, and assignment operators](operators.md) — the equality operator survey alongside arithmetic, logical, and assignment operators. diff --git a/docs/csharp/fundamentals/expressions/index.md b/docs/csharp/fundamentals/expressions/index.md index d36c29dae0f52..a8eb292482c94 100644 --- a/docs/csharp/fundamentals/expressions/index.md +++ b/docs/csharp/fundamentals/expressions/index.md @@ -100,6 +100,7 @@ For a broader look at null-safe operators, see [C# null operators](../null-safet ## See also - [C# operators and expressions (language reference)](../../language-reference/operators/index.md) — full precedence table and every operator +- [Arithmetic, comparison, logical, and assignment operators](operators.md) — the everyday operators in depth - [Equality comparisons](equality.md) — how `==`, `!=`, and `Equals` work - [C# null operators](../null-safety/null-operators.md) — `?.`, `??`, and `??=` - [Boolean logical operators](../../language-reference/operators/boolean-logical-operators.md) diff --git a/docs/csharp/fundamentals/expressions/operators.md b/docs/csharp/fundamentals/expressions/operators.md new file mode 100644 index 0000000000000..70deeda36e76d --- /dev/null +++ b/docs/csharp/fundamentals/expressions/operators.md @@ -0,0 +1,163 @@ +--- +title: "C# arithmetic, comparison, logical, and assignment operators" +description: Learn how C# arithmetic, relational, equality, logical, conditional, and assignment operators work, including integer division, short-circuit evaluation, and compound assignment. +ms.date: 08/18/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# C# operators + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. +> +> **Coming from another language?** Most operators in this article (`+`, `-`, `*`, `/`, `%`, `&&`, `||`, `!`, `==`, `!=`, `<`, `>`, comparison operators, and `=`) work the same as in Java, C++, and JavaScript. The main surprises for newcomers are integer division behavior, the prefix/postfix distinction for `++`/`--`, and the way compound assignment converts back to the left-hand-side type. + +An *operator* combines one or more *operands* into a single value. You already know about expressions and operator precedence from [C# expressions](index.md); this article goes deeper into the specific operators you'll use every day. + +## Arithmetic operators + +The five arithmetic operators perform numeric calculations. + +| Operator | Name | Example | Result | +|----------|----------------|----------|--------| +| `+` | Addition | `10 + 3` | `13` | +| `-` | Subtraction | `10 - 3` | `7` | +| `*` | Multiplication | `10 * 3` | `30` | +| `/` | Division | `10 / 3` | `3` | +| `%` | Remainder | `10 % 3` | `1` | + +:::code language="csharp" source="snippets/operators/Program.cs" ID="ArithmeticOps"::: + +**Integer division truncates toward zero.** When both operands are integers, `/` discards the fractional part: `7 / 2` is `3`, not `3.5`. Truncation is toward zero, not toward the smaller number: `-7 / 2` is `-3` (not `-4`). To get a decimal result, make at least one operand a floating-point type: `7.0 / 2` is `3.5`. This differs from some languages where `/` always produces a floating-point result. + +**Remainder (`%`) returns what's left over** after integer division: `10 % 3` is `1` because `10 = 3 × 3 + 1`. It's useful for cycling through a fixed range (`index % length`), testing divisibility (`n % 2 == 0`), and extracting digits. With negative operands, the sign of the result matches the sign of the *dividend* (the left operand): `-7 % 3` is `-1` and `7 % -3` is `1`. + +## Unary operators + +Unary operators act on a single operand. + +:::code language="csharp" source="snippets/operators/Program.cs" ID="UnaryOps"::: + +- `+x` (unary plus) — leaves the value unchanged; rarely written explicitly but valid. +- `-x` (unary minus) — negates the value. +- `!x` (logical NOT) — flips `true` to `false` and `false` to `true`. You'll use `!` often: `if (!list.Contains(item))`. + +## Increment and decrement + +`++` adds 1 and `--` subtracts 1. Both have a *prefix* form and a *postfix* form that differ in which value is returned: + +:::code language="csharp" source="snippets/operators/Program.cs" ID="IncrementDecrement"::: + +- **Prefix** (`++i`, `--i`): increments or decrements the variable first, then returns the *new* value. +- **Postfix** (`i++`, `i--`): returns the *current* value first, then increments or decrements the variable. + +When `++` or `--` appears as a standalone statement (not part of a larger expression), prefix and postfix have the same effect. The distinction matters only when the result is used — for example, in an assignment or as a method argument. + +## Relational operators + +Relational operators compare two values and return a `bool`. + +| Operator | Meaning | Example | +|----------|-----------------------|-----------------| +| `<` | Less than | `speed < limit` | +| `>` | Greater than | `speed > limit` | +| `<=` | Less than or equal | `score <= 100` | +| `>=` | Greater than or equal | `score >= 0` | + +:::code language="csharp" source="snippets/operators/Program.cs" ID="RelationalOps"::: + +Relational operators work on all numeric types and `char`. For `char`, comparison uses the character's numeric Unicode code point value, not any alphabetical or domain-specific ordering. In the grade example above, `'B'` is greater than or equal to `'A'` because `'B'` has Unicode value 66 and `'A'` has Unicode value 65 — the *numbers* determine the comparison, not the meaning of the letter grades. + +## Equality operators + +`==` and `!=` check whether two values are equal or not. `!=` is `true` when the operands are **not** equal, and `false` when they are. + +:::code language="csharp" source="snippets/operators/Program.cs" ID="EqualityOps"::: + +For numeric types and `string`, equality tests the values. For reference types, the default is identity (whether two variables point to the same object), but many types including `string` and `record` override this to compare content. For the full picture — how equality works across value types, reference types, records, and structs — see [Equality comparisons](equality.md). + +> [!NOTE] +> C# doesn't have a `===` operator. Writing `===` is a compile-time error: +> +> ```csharp +> // This does not compile — C# has no === operator +> bool same = (x === 10); +> ``` +> +> If you're coming from JavaScript, use `==` for value comparison (C# `==` already compares by value for primitive types and strings). A common related bug is accidentally writing `=` (assignment) where you meant `==` (equality check). The compiler catches the most common forms, but double-check any `if` condition that contains `=`. + +## Conditional-logical operators + +`&&` (AND) and `||` (OR) combine `bool` expressions. + +:::code language="csharp" source="snippets/operators/Program.cs" ID="LogicalOps"::: + +Both operators *short-circuit*: they skip evaluating the right operand when the result is already determined. + +- `&&` returns `false` as soon as the left side is `false`. The right side is never evaluated. +- `||` returns `true` as soon as the left side is `true`. The right side is never evaluated. + +Short-circuit behavior has a practical benefit: you can safely guard an operation on the right side with a null check on the left side, as the example above shows. If `items` is `null`, the `&&` stops there — `items.Count` is never called, so no `NullReferenceException` is thrown. + +## Conditional operator `?:` + +The conditional operator (also called the *ternary* operator) evaluates one of two expressions based on a condition: + +``` +condition ? value-when-true : value-when-false +``` + +:::code language="csharp" source="snippets/operators/Program.cs" ID="ConditionalOp"::: + +The `?:` operator always evaluates exactly one branch — the side that doesn't match the condition is never evaluated. This makes it safe to use an expression on one side that would fail for other inputs, as long as the condition properly guards it. + +Use `?:` for simple, inline choices. For multi-way conditions or blocks of code, an `if`/`else` statement is usually clearer. + +## Assignment operators + +The simple assignment operator `=` stores a value in a variable: + +```csharp +int level = 1; // declaration + initialization +level = 5; // reassignment +``` + +Assignment in C# is *right-associative*, which means `a = b = c = 0` evaluates right to left: `c` gets `0`, then `b` gets `0`, then `a` gets `0`. + +### Compound assignment + +Compound assignment operators combine a binary operation with assignment: + +| Operator | Equivalent to | +|----------|---------------| +| `x += y` | `x = x + y` | +| `x -= y` | `x = x - y` | +| `x *= y` | `x = x * y` | +| `x /= y` | `x = x / y` | +| `x %= y` | `x = x % y` | + +:::code language="csharp" source="snippets/operators/Program.cs" ID="AssignmentOps"::: + +Compound assignment is more than just a shorthand. It evaluates the left-hand side **exactly once** and then converts the result back to the left-hand-side type. This matters when the left side has side effects (like an array indexer), and it's why compound assignment on a `byte` variable compiles without an explicit cast while the expanded form does not: + +:::code language="csharp" source="snippets/operators/Program.cs" ID="AssignmentChain"::: + +`small += 10` compiles because the compiler inserts the narrowing conversion automatically — the result, `210`, fits within the `byte` range of 0–255. `small = small + 10` would require an explicit `(byte)` cast, because the arithmetic promotes both operands to `int`. + +## Other C# operators + +This article covers the operators you'll encounter most in everyday code. The C# language includes more operators useful in specific scenarios: + +- **Shift operators** (`<<`, `>>`, `>>>`) — shift the bits of an integer value left or right by a specified number of positions. **Bitwise and integer logical operators** (`&`, `|`, `^`, `~`) — combine or invert integer values one bit at a time, useful in flags, masks, and low-level code: [Bitwise and shift operators](../../language-reference/operators/bitwise-and-shift-operators.md) +- **`checked` and `unchecked`** — control whether integer overflow throws an exception (`checked`) or wraps silently (`unchecked`): [Checked and unchecked](../../language-reference/statements/checked-and-unchecked.md) +- **Null operators** (`??`, `??=`, `?.`, `?[]`) — safely handle `null` values by providing defaults or short-circuiting member access: [Null operators](../null-safety/null-operators.md) +- **Type-test and conversion operators** (`is`, `as`, `typeof`, cast `(T)`) — check or convert a value's runtime type: [Type-testing and cast operators](../../language-reference/operators/type-testing-and-cast.md) +- **Range and index operators** (`..`, `^`) — create ranges and end-relative indexes for slicing arrays and spans: [Member access and null-conditional operators](../../language-reference/operators/member-access-operators.md) +- **Deconstruction assignment** — unpack a tuple or type into individual variables in a single expression: [Deconstructing tuples and other types](../../fundamentals/functional/deconstruct.md) + +## See also + +- [C# expressions](index.md) — how expressions form and how operator precedence works +- [Equality comparisons](equality.md) — how `==`, `!=`, and `Equals` work across different types +- [C# operators and expressions (language reference)](../../language-reference/operators/index.md) — full precedence table and every operator diff --git a/docs/csharp/fundamentals/expressions/snippets/equality/Program.cs b/docs/csharp/fundamentals/expressions/snippets/equality/Program.cs index 670765639cd50..f1855e61cc859 100644 --- a/docs/csharp/fundamentals/expressions/snippets/equality/Program.cs +++ b/docs/csharp/fundamentals/expressions/snippets/equality/Program.cs @@ -41,14 +41,6 @@ Console.WriteLine(t1 == t2); // => True // -// -var red1 = new Color(255, 0, 0); -var red2 = new Color(255, 0, 0); - -Console.WriteLine(red1.Equals(red2)); // => True -Console.WriteLine(red1 == red2); // => False (no == overload; identity check) -// - // var doc1 = new Document("Report"); var doc2 = new Document("Report"); @@ -58,6 +50,15 @@ Console.WriteLine(ReferenceEquals(doc1, doc3)); // => True // +// +var playlist1 = new Playlist("Chill", new List { "Song A", "Song B" }); +var playlist2 = new Playlist("Chill", new List { "Song A", "Song B" }); + +Console.WriteLine(playlist1.Equals(playlist2)); // => False (different List instances) +Console.WriteLine(playlist1.Tracks.SequenceEqual(playlist2.Tracks)); // => True +// + + // ── Type declarations ──────────────────────────────────────────────────────── class Order(int id, string name) @@ -76,30 +77,10 @@ record Person(string First, string Last); record struct Dimension(double Width, double Height); -// -class Color : IEquatable -{ - public Color(int r, int g, int b) - { - R = r; - G = g; - B = b; - } - - public int R { get; } - public int G { get; } - public int B { get; } - - public bool Equals(Color? other) => - other is not null && R == other.R && G == other.G && B == other.B; - - public override bool Equals(object? obj) => obj is Color other && Equals(other); - public override int GetHashCode() => HashCode.Combine(R, G, B); -} -// - class Document(string title) { public string Title { get; } = title; } + +record Playlist(string Name, List Tracks); diff --git a/docs/csharp/fundamentals/expressions/snippets/operators/Program.cs b/docs/csharp/fundamentals/expressions/snippets/operators/Program.cs new file mode 100644 index 0000000000000..963c8f34e4615 --- /dev/null +++ b/docs/csharp/fundamentals/expressions/snippets/operators/Program.cs @@ -0,0 +1,155 @@ +// +int apples = 10; +int oranges = 3; + +Console.WriteLine(apples + oranges); // => 13 (addition) +Console.WriteLine(apples - oranges); // => 7 (subtraction) +Console.WriteLine(apples * oranges); // => 30 (multiplication) +Console.WriteLine(apples / oranges); // => 3 (integer division: truncates toward zero) +Console.WriteLine(apples % oranges); // => 1 (remainder) + +// Integer division always truncates toward zero — the fractional part is discarded +int result = 7 / 2; +Console.WriteLine(result); // => 3, not 3.5 + +// Truncation applies to negative results too: -7 / 2 is -3, not -4 +int negResult = -7 / 2; +Console.WriteLine(negResult); // => -3 + +// To get a decimal result, at least one operand must be a double or float +double precise = 7.0 / 2; +Console.WriteLine(precise); // => 3.5 + +// Remainder with negative operands: the sign of the result matches the dividend +Console.WriteLine(-7 % 3); // => -1 (-7 = 3 × -2 + (-1)) +Console.WriteLine(7 % -3); // => 1 ( 7 = -3 × -2 + 1) +// + +// +int temperature = 20; +int windChill = -5; + +int heatIndex = +temperature; // unary +: value unchanged (rarely needed) +int coldFactor = -windChill; // unary -: negates the value → 5 + +Console.WriteLine(heatIndex); // => 20 +Console.WriteLine(coldFactor); // => 5 + +bool isRaining = false; +bool isSunny = !isRaining; // logical NOT: flips true/false +Console.WriteLine(isSunny); // => True +// + +// +int counter = 5; + +// Prefix: increment first, then use the new value +int a = ++counter; +Console.WriteLine(a); // => 6 +Console.WriteLine(counter); // => 6 + +// Postfix: use the current value first, then increment +int b = counter++; +Console.WriteLine(b); // => 6 (value before increment) +Console.WriteLine(counter); // => 7 (incremented after) + +// Decrement works the same way +int score = 10; +Console.WriteLine(score--); // => 10 (current value; score becomes 9) +Console.WriteLine(score); // => 9 +// + +// +int speed = 75; +int limit = 60; + +Console.WriteLine(speed > limit); // => True (greater than) +Console.WriteLine(speed < limit); // => False (less than) +Console.WriteLine(speed >= limit); // => True (greater than or equal) +Console.WriteLine(speed <= limit); // => False (less than or equal) + +// Relational operators work on all numeric types and char +// char comparison uses the character's numeric Unicode code point, not alphabetical position +// 'B' (U+0042, value 66) is less than 'A' (U+0041, value 65)? No — 'A' (65) < 'B' (66) +char grade = 'B'; +Console.WriteLine(grade >= 'A' && grade <= 'C'); // => True ('A'=65 <= 'B'=66 <= 'C'=67) +// + +// +int expected = 42; +int actual = 42; + +Console.WriteLine(actual == expected); // => True (values are equal) +Console.WriteLine(actual != expected); // => False (true when values are not equal) + +string name = "Alice"; +Console.WriteLine(name == "Alice"); // => True (string content matches) +Console.WriteLine(name == "alice"); // => False (case-sensitive) + +int x = 5; +Console.WriteLine(x == 10); // => False +// + +// +int age = 20; +bool hasTicket = true; + +// && (AND): both sides must be true +bool canEnter = age >= 18 && hasTicket; +Console.WriteLine(canEnter); // => True + +// || (OR): at least one side must be true +bool freeEntry = age < 5 || age >= 65; +Console.WriteLine(freeEntry); // => False + +// Short-circuit: right side is skipped when the result is already determined +// Here, items.Count is never called if items is null +List? items = null; +bool hasItems = items != null && items.Count > 0; +Console.WriteLine(hasItems); // => False (short-circuits; no NullReferenceException) +// + +// +int temperature2 = 35; + +// condition ? value-when-true : value-when-false +string weather = temperature2 > 30 ? "hot" : "comfortable"; +Console.WriteLine(weather); // => hot + +// Only the matching branch evaluates — the other branch is never run +int divisor = 0; +// The division 10 / divisor is never evaluated because divisor == 0 is true +int safe = divisor == 0 ? -1 : 10 / divisor; +Console.WriteLine(safe); // => -1 +// + +// +int level = 1; +level = 5; // simple assignment: replaces the value +Console.WriteLine(level); // => 5 + +// Compound assignment: short form of binary operation + assignment +int hp = 100; +hp += 20; // same as: hp = hp + 20 +Console.WriteLine(hp); // => 120 +hp -= 10; // same as: hp = hp - 10 +Console.WriteLine(hp); // => 110 +hp *= 2; // same as: hp = hp * 2 +Console.WriteLine(hp); // => 220 +hp /= 3; // same as: hp = hp / 3 (integer division) +Console.WriteLine(hp); // => 73 +hp %= 7; // same as: hp = hp % 7 +Console.WriteLine(hp); // => 3 +// + +// +// Assignment is right-associative: evaluated right to left +int a2, b2, c2; +a2 = b2 = c2 = 0; // c2 = 0 first, then b2 = 0, then a2 = 0 +Console.WriteLine($"{a2} {b2} {c2}"); // => 0 0 0 + +// Compound assignment evaluates the left side once and converts back to the LHS type +byte small = 200; +small += 10; // equivalent to: small = (byte)(small + 10); result is 210 +Console.WriteLine(small); // => 210 +// diff --git a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityStruct/ValueEqualityStruct.csproj b/docs/csharp/fundamentals/expressions/snippets/operators/operators.csproj similarity index 80% rename from docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityStruct/ValueEqualityStruct.csproj rename to docs/csharp/fundamentals/expressions/snippets/operators/operators.csproj index f704bf4988fa6..dfb40caafcf9a 100644 --- a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityStruct/ValueEqualityStruct.csproj +++ b/docs/csharp/fundamentals/expressions/snippets/operators/operators.csproj @@ -2,9 +2,9 @@ Exe - net8.0 - enable + net10.0 enable + enable diff --git a/docs/csharp/fundamentals/object-oriented/objects.md b/docs/csharp/fundamentals/object-oriented/objects.md index ed0e19f474ef8..cd2e20e561a84 100644 --- a/docs/csharp/fundamentals/object-oriented/objects.md +++ b/docs/csharp/fundamentals/object-oriented/objects.md @@ -8,44 +8,44 @@ helpviewer_keywords: --- # Objects - create instances of types -A class or struct definition is like a blueprint that specifies what the type can do. An object is basically a block of memory that is allocated and configured according to the blueprint. A program might create many objects of the same class. Objects are also called instances, and they can be stored in either a named variable or in an array or collection. Client code is the code that uses these variables to call the methods and access the public properties of the object. In an object-oriented language such as C#, a typical program consists of multiple objects interacting dynamically. +A class or struct definition is like a blueprint that specifies what the type can do. An object is a block of memory that the program allocates and configures according to the blueprint. A program might create many objects of the same class. You can also call objects instances. You can store them in a named variable or in an array or collection. Client code uses these variables to call the methods and access the public properties of the object. In an object-oriented language such as C#, a typical program consists of multiple objects interacting dynamically. > [!NOTE] -> Static types behave differently than what is described here. For more information, see [Static Classes and Static Class Members](../../programming-guide/classes-and-structs/static-classes-and-static-class-members.md). +> Static types behave differently than what is described in this article. For more information, see [Static Classes and Static Class Members](../../programming-guide/classes-and-structs/static-classes-and-static-class-members.md). -## Struct Instances vs. Class Instances +## Struct instances vs. class instances -Because classes are reference types, a variable of a class object holds a reference to the address of the object on the managed heap. If a second variable of the same type is assigned to the first variable, then both variables refer to the object at that address. This point is discussed in more detail later in this article. +Because classes are reference types, a variable of a class object holds a reference to the address of the object on the managed heap. If you assign a second variable of the same type to the first variable, both variables refer to the object at that address. This article discusses this point in more detail later. -Instances of classes are created by using the [`new` operator](../../language-reference/operators/new-operator.md). In the following example, `Person` is the type and `person1` and `person2` are instances, or objects, of that type. +You create instances of classes by using the [`new` operator](../../language-reference/operators/new-operator.md). In the following example, `Person` is the type and `person1` and `person2` are instances, or objects, of that type. :::code language="csharp" source="./snippets/objects/Program.cs"::: -Because structs are value types, a variable of a struct object holds a copy of the entire object. Instances of structs can also be created by using the `new` operator, but this isn't required, as shown in the following example: +Because structs are value types, a variable of a struct object holds a copy of the entire object. You can also create instances of structs by using the `new` operator, but you don't need to use it, as shown in the following example: :::code language="csharp" source="./snippets/objects/Application.cs"::: -The memory for both `p1` and `p2` is allocated on the thread stack. That memory is reclaimed along with the type or method in which it's declared. This is one reason why structs are copied on assignment. By contrast, the memory that is allocated for a class instance is automatically reclaimed (garbage collected) by the common language runtime when all references to the object are out of scope. It isn't possible to deterministically destroy a class object like you can in C++. For more information about garbage collection in .NET, see [Garbage Collection](../../../standard/garbage-collection/index.md). +The thread stack allocates memory for both `p1` and `p2`. The program reclaims that memory along with the type or method in which you declare it. This memory management is one reason why structs are copied on assignment. By contrast, the common language runtime automatically reclaims (garbage collects) the memory it allocates for a class instance when all references to the object go out of scope. You can't deterministically destroy a class object like you can in C++. For more information about garbage collection in .NET, see [Garbage Collection](../../../standard/garbage-collection/index.md). > [!NOTE] -> The allocation and deallocation of memory on the managed heap is highly optimized in the common language runtime. In most cases, there's no significant difference in the performance cost of allocating a class instance on the heap versus allocating a struct instance on the stack. +> The common language runtime highly optimizes the allocation and deallocation of memory on the managed heap. In most cases, there's no significant difference in the performance cost of allocating a class instance on the heap versus allocating a struct instance on the stack. -## Object Identity vs. Value Equality +## Object identity vs. value equality -When you compare two objects for equality, you must first distinguish whether you want to know whether the two variables represent the same object in memory, or whether the values of one or more of their fields are equivalent. If you're intending to compare values, you must consider whether the objects are instances of value types (structs) or reference types (classes, delegates, arrays). +When you compare two objects for equality, first decide whether you want to know if the two variables represent the same object in memory or if the values of one or more of their fields are equivalent. If you want to compare values, consider whether the objects are instances of value types (structs) or reference types (classes, delegates, arrays). -- To determine whether two class instances refer to the same location in memory (which means that they have the same *identity*), use the static method. ( is the implicit base class for all value types and reference types, including user-defined structs and classes.) -- The method, by default, determines whether the instance fields in two struct instances have the same values. Because all structs implicitly inherit from , you call the method directly on your object as shown in the following example: +- Use the static method to determine whether two class instances refer to the same location in memory (which means that they have the same *identity*). ( is the implicit base class for all value types and reference types, including user-defined structs and classes.) +- By default, the method determines whether the instance fields in two struct instances have the same values. Because all structs implicitly inherit from , you call the method directly on your object as shown in the following example: :::code language="csharp" source="./snippets/objects/Equality.cs" ID="Snippet32"::: - The default implementation of `Equals` uses boxing and reflection in some cases. For information about how to provide an efficient equality algorithm that's specific to your type, see [How to define value equality for a type](../../programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md). Records are reference types that use value semantics for equality. + The default implementation of `Equals` uses boxing and reflection in some cases. For information about how to provide an efficient equality algorithm that's specific to your type, see [Implement equality yourself when a type can't be a record](../../language-reference/operators/equality-operators.md#implement-equality-yourself-when-a-type-cant-be-a-record). Records are reference types that use value semantics for equality. -- To determine whether the values of the fields in two class instances are equal, you might be able to use the method or the [== operator](../../language-reference/operators/equality-operators.md#equality-operator-). However, only use them if the class has overridden or overloaded them to provide a custom definition of what "equality" means for objects of that type. The class might also implement the interface or the interface. Both interfaces provide methods that can be used to test value equality. When designing your own classes that override `Equals`, make sure to follow the guidelines stated in [How to define value equality for a type](../../programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md) and . +- To determine whether the values of the fields in two class instances are equal, you might be able to use the method or the [== operator](../../language-reference/operators/equality-operators.md#equality-operator-). However, only use them if the class has overridden or overloaded them to provide a custom definition of what "equality" means for objects of that type. The class might also implement the interface or the interface. Both interfaces provide methods that can be used to test value equality. When designing your own classes that override `Equals`, make sure to follow the guidelines stated in [Implement equality yourself when a type can't be a record](../../language-reference/operators/equality-operators.md#implement-equality-yourself-when-a-type-cant-be-a-record) and . -## Related Sections +## Related sections -For more information: +For more information, see: - [Classes](../types/classes.md) - [Constructors](../../programming-guide/classes-and-structs/constructors.md) diff --git a/docs/csharp/how-to/index.md b/docs/csharp/how-to/index.md index 9966fb8880776..342638eab8fb0 100644 --- a/docs/csharp/how-to/index.md +++ b/docs/csharp/how-to/index.md @@ -67,8 +67,8 @@ You may need to convert an object to a different type. You may create types that define their own rules for equality or define a natural ordering among objects of that type. -- [Test for reference-based equality](../programming-guide/statements-expressions-operators/how-to-test-for-reference-equality-identity.md). -- [Define value-based equality for a type](../programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md). +- [Test for reference-based equality](../fundamentals/expressions/equality.md#use-objectreferenceequals-to-test-identity-directly). +- [Define value-based equality for a type](../language-reference/operators/equality-operators.md#implement-equality-yourself-when-a-type-cant-be-a-record). ## Exception handling diff --git a/docs/csharp/language-reference/compiler-messages/overloaded-operator-errors.md b/docs/csharp/language-reference/compiler-messages/overloaded-operator-errors.md index ccb5aa518b072..9e4953d6c799d 100644 --- a/docs/csharp/language-reference/compiler-messages/overloaded-operator-errors.md +++ b/docs/csharp/language-reference/compiler-messages/overloaded-operator-errors.md @@ -107,8 +107,8 @@ ai-usage: ai-assisted This article covers the following compiler errors and warnings: - - [**CS0031**](#overflow-and-underflow-errors): *Constant value 'value' cannot be converted to a 'type'* - [**CS0056**](#inconsistent-accessibility): *Inconsistent accessibility: return type 'type' is less accessible than operator 'operator'* @@ -231,7 +231,7 @@ All types used in a public operator's signature must be at least as accessible a The C# language restricts which types can participate in user-defined conversions. For the full rules, see [User-defined conversion operators](../operators/user-defined-conversion-operators.md) and [Conversion operators](~/_csharpstandard/standard/classes.md#15104-conversion-operators) in the C# specification. -- Remove the conversion operator that converts to or from an interface type (**CS0552**). The language prohibits user-defined conversions involving interface types because interface conversions are handled through the type system's reference conversions and boxing. Use explicit interface implementations or helper methods instead. +- Remove the conversion operator that converts to or from an interface type (**CS0552**). The language prohibits user-defined conversions involving interface types because the type system handles interface conversions through reference conversions and boxing. Use explicit interface implementations or helper methods instead. - Remove the conversion operator that converts to or from a base class (**CS0553**). Conversions between a type and its base class already exist through implicit reference conversions (upcast) and explicit reference conversions (downcast), so a user-defined conversion would create ambiguity. - Remove the conversion operator that converts to or from a derived class (**CS0554**). Like base class conversions, conversions between a type and its derived types are built into the language through inheritance, and user-defined conversions would conflict with them. - Remove the conversion operator that converts the enclosing type to itself (**CS0555**). Every type already has an implicit identity conversion to itself, so a user-defined conversion from a type to the same type is redundant and not permitted. @@ -275,7 +275,7 @@ The compiler enforces strict matching between operator declarations and the inte - Change the implementing member to an operator declaration that matches the interface's operator member, or change the interface member to a method if the implementing member is a method (**CS9311**). An operator can only implement an interface member that's also declared as an operator—you can't satisfy an operator contract with a regular method, or vice versa. - Change the overriding member to an operator declaration that matches the base class's operator member, or change the base class member to a method if the derived class member is a method (**CS9312**). Like interface implementation, an override must match the kind of member being overridden—an operator can't override a non-operator member. -- Change the compound assignment operator declaration to accept exactly one parameter (**CS9313**). Compound assignment operators are instance members where the left operand is implicitly `this`, so only the right-hand operand is declared as a parameter. +- Change the compound assignment operator declaration to accept exactly one parameter (**CS9313**). Compound assignment operators are instance members where the left operand is implicitly `this`, so you only declare the right-hand operand as a parameter. ## Equality operators @@ -283,7 +283,7 @@ The compiler enforces strict matching between operator declarations and the inte - **CS0660**: *Type defines operator == or operator != but doesn't override Object.Equals(object o)* - **CS0661**: *Type defines operator == or operator != but doesn't override Object.GetHashCode()* -The compiler requires that equality-related overrides and operator definitions stay in sync. When you override or define `operator ==` / `operator !=`, you must also provide the related overrides. For the full rules, see [How to define value equality for a type](../../programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md) and [Equality operators](../operators/equality-operators.md). +The compiler requires that equality-related overrides and operator definitions stay in sync. When you override or define `operator ==` / `operator !=`, you must also provide the related overrides. For the full rules, see [Implement equality yourself when a type can't be a record](../operators/equality-operators.md#implement-equality-yourself-when-a-type-cant-be-a-record) and [Equality operators](../operators/equality-operators.md). - Add an override of when you override (**CS0659**). Hash-based collections like and rely on the contract that two objects that are equal must return the same hash code. Without a matching `GetHashCode` override, objects that compare as equal might hash to different buckets, causing lookups and deduplication to fail silently. - Add an override of when you define `operator ==` or `operator !=` (**CS0660**). Code that calls `Equals` directly—including many framework APIs, LINQ methods, and collection operations—won't use your custom operator. Without a consistent `Equals` override, the same two objects might be considered equal by `==` but not by `Equals`, leading to unpredictable behavior. 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 4ab09d24a9d5a..4f4fb86a11542 100644 --- a/docs/csharp/language-reference/compiler-messages/record-declaration-errors.md +++ b/docs/csharp/language-reference/compiler-messages/record-declaration-errors.md @@ -54,7 +54,7 @@ ai-usage: ai-assisted The C# compiler generates errors and warnings when you misuse [record types](../builtin-types/record.md). Record types provide built-in members that implement value-based equality. These diagnostics help you follow the rules for declaring and using record types. - - [**CS8851**](#equality-members): *'type' defines 'Equals' but not 'GetHashCode'* @@ -134,11 +134,11 @@ To correct these errors, apply the following changes to your positional record d - **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). +[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 [C# equality comparisons](../../fundamentals/expressions/equality.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**). +- Add a `GetHashCode` method whenever you define an `Equals` method. The [equivalence contract](../operators/equality-operators.md#implement-equality-yourself-when-a-type-cant-be-a-record) 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 `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**). diff --git a/docs/csharp/language-reference/operators/equality-operators.md b/docs/csharp/language-reference/operators/equality-operators.md index 0014853addcad..e8edfb54fadf9 100644 --- a/docs/csharp/language-reference/operators/equality-operators.md +++ b/docs/csharp/language-reference/operators/equality-operators.md @@ -1,7 +1,7 @@ --- title: "Equality operators - test if two objects are equal or not equal" -description: "C# equality operators test if two objects are equal or not equal. You can define equality operators for your types for custom comparisons for equality" -ms.date: 01/20/2026 +description: "C# equality operators test if two objects are equal or not equal. You can define equality operators for your types for custom comparisons for equality. Learn how to implement value equality correctly in sealed types and unsealed class hierarchies." +ms.date: 08/19/2026 author: pkulikov f1_keywords: - "==_CSharpKeyword" @@ -15,6 +15,8 @@ helpviewer_keywords: - "inequality operator [C#]" - "not equals operator [C#]" - "!= operator [C#]" + - "equality in class hierarchies [C#]" + - "polymorphic equality [C#]" --- # Equality operators - test if two objects are equal or not @@ -49,7 +51,7 @@ By default, reference-type operands, excluding records, are equal if they refer :::code language="csharp" source="snippets/shared/EqualityOperators.cs" id="ReferenceTypesEquality"::: -As the example shows, user-defined reference types support the `==` operator by default. However, a reference type can overload the `==` operator. If a reference type overloads the `==` operator, use the method to check if two references of that type refer to the same object. +As the preceding example shows, user-defined reference types support the `==` operator by default. However, a reference type can overload the `==` operator. If a reference type overloads the `==` operator, use the method to check if two references of that type refer to the same object. ### Record types equality @@ -92,6 +94,66 @@ The following example demonstrates how to use the `!=` operator: :::code language="csharp" source="snippets/shared/EqualityOperators.cs" id="NonEquality"::: +## Equality in class hierarchies + +Records handle inheritance correctly without manual work. The compiler-generated equality checks both runtime type and all declared properties, so it automatically satisfies the symmetry and transitivity requirements. Prefer `record` over a manual unsealed hierarchy when value equality is the goal. + +> [!IMPORTANT] +> Use `record` whenever possible — the compiler generates all required equality members for you. Manual implementation is only needed when your type must derive from a non-record class or has other constraints that prevent `record`. + +### Implement equality yourself when a type can't be a record + +Here is a minimal manual implementation for a value type that can't be a record: + +:::code language="csharp" source="snippets/EqualityHierarchies/Program.cs" id="ColorDefinition"::: + +The implementation provides three required members: `Equals(T?)` as the core comparison, `override Equals(object?)` for object-level calls, and `override GetHashCode()` so hash-based collections work correctly. `HashCode.Combine` is a library helper that builds one hash from the same values used by `Equals`. Implementing (the `Equals(T?)` overload) is optional but avoids boxing when callers already have the concrete type. + +When you also define `==` and `!=`, the language requires them as a pair; warnings [CS0660](../../language-reference/compiler-messages/overloaded-operator-errors.md#equality-operators) and [CS0661](../../language-reference/compiler-messages/overloaded-operator-errors.md#equality-operators) remind you to keep all four members consistent. + +With the three members above in place, `Equals` reflects value equality, but `==` still tests identity because no `==` operator has been declared yet: + +:::code language="csharp" source="snippets/EqualityHierarchies/Program.cs" id="IEquatableUsage"::: + +A correct implementation must also satisfy the *equivalence contract* (assume `x`, `y`, and `z` are non-null): + +1. **Reflexive**: `x.Equals(x)` returns `true`. +2. **Symmetric**: `x.Equals(y)` returns the same value as `y.Equals(x)`. +3. **Transitive**: if `x.Equals(y)` and `y.Equals(z)` are both `true`, then `x.Equals(z)` must be `true`. +4. **Consistent**: successive calls to `x.Equals(y)` return the same value as long as neither object changes. +5. **Null behavior**: `x.Equals(null)` returns `false`; `x.Equals(y)` must not throw when called on a non-null `x`. + +Value equality in an unsealed class hierarchy requires more care than in a sealed class to satisfy the symmetric and transitive rules. The hazard is that `IEquatable.Equals(T? other)` dispatch follows the *declared type* (the type written in the variable declaration) of the variable, not its runtime type. If `Shape` declares a non-`virtual` `Equals(Shape? other)`, a variable typed as `Shape` that holds a `Circle` at runtime invokes `Shape.Equals`—silently ignoring `Circle`-specific fields. Two `Circle` objects with different radii can compare as equal when accessed through a `Shape` variable. + +The correct pattern requires two cooperating requirements: make the typed `Equals` method `virtual` so each derived class can extend the comparison, and add a `GetType() == other.GetType()` guard in the base-class implementation so objects of different runtime types are never considered equal. + +### Base class implementation + +:::code language="csharp" source="snippets/EqualityHierarchies/Program.cs" id="HierarchyShapeDefinition"::: + +Key points: + +- **`virtual` typed `Equals`**: each derived class overrides this method to augment the comparison with its own fields. +- **`GetType()` guard**: `GetType() == other.GetType()` prevents a `Circle` from equaling a `Shape` with the same color, and prevents objects of different derived types from equaling each other. +- **`GetHashCode` includes `GetType()`**: because two objects are equal only when their runtime types match, `GetHashCode` must hash the runtime type as well as the data fields. Omitting `GetType()` here causes incorrect behavior in `Dictionary` and `HashSet`. +- **`==` delegates to `Equals`**: keeps operator and method equality consistent. + +### Derived class implementation + +A derived class that adds fields overrides the typed `Equals`, casts to its own type, calls `base.Equals`, then compares its own fields: + +:::code language="csharp" source="snippets/EqualityHierarchies/Program.cs" id="HierarchyCircleDefinition"::: + +`base.Equals(c)` enforces the `GetType()` guard and checks the shared fields. The cast via `other is Circle c` fails fast when the argument is a `Shape` of any other derived type. + +### Usage through a base-type variable + +:::code language="csharp" source="snippets/EqualityHierarchies/Program.cs" id="HierarchyUsage"::: + +### Sealed classes are simpler + +You can't subclass a `sealed` class, so compile-time and runtime types always agree. You don't need the `GetType()` guard or `virtual` dispatch. The `IEquatable` pattern shown in [Implement equality yourself when a type can't be a record](#implement-equality-yourself-when-a-type-cant-be-a-record) is correct and complete for a sealed class. + ## Operator overloadability You can [overload](operator-overloading.md) the `==` and `!=` operators in a user-defined type. If you overload one of these two operators, you must also overload the other operator. @@ -114,5 +176,5 @@ For more information about equality of record types, see the [Equality members]( - - - -- [Equality comparisons](../../programming-guide/statements-expressions-operators/equality-comparisons.md) +- [Equality comparisons](../../fundamentals/expressions/equality.md) - [Comparison operators](comparison-operators.md) diff --git a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/RecordCollectionsIssue.csproj b/docs/csharp/language-reference/operators/snippets/EqualityHierarchies/EqualityHierarchies.csproj similarity index 80% rename from docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/RecordCollectionsIssue.csproj rename to docs/csharp/language-reference/operators/snippets/EqualityHierarchies/EqualityHierarchies.csproj index fd4dd4565750e..5c0a78df5ac6b 100644 --- a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/RecordCollectionsIssue.csproj +++ b/docs/csharp/language-reference/operators/snippets/EqualityHierarchies/EqualityHierarchies.csproj @@ -2,9 +2,9 @@ Exe - net8.0 - enable + net10.0 enable + enable \ No newline at end of file diff --git a/docs/csharp/language-reference/operators/snippets/EqualityHierarchies/Program.cs b/docs/csharp/language-reference/operators/snippets/EqualityHierarchies/Program.cs new file mode 100644 index 0000000000000..7cf089c22128b --- /dev/null +++ b/docs/csharp/language-reference/operators/snippets/EqualityHierarchies/Program.cs @@ -0,0 +1,82 @@ +// +var red1 = new Color(255, 0, 0); +var red2 = new Color(255, 0, 0); + +Console.WriteLine(red1.Equals(red2)); // => True +Console.WriteLine(red1 == red2); // => False (no == overload; identity check) +// + +// +Shape circle1 = new Circle("red", 5.0); +Shape circle2 = new Circle("red", 7.0); +Shape circle3 = new Circle("red", 5.0); +Shape shape1 = new Shape("red"); + +Console.WriteLine(circle1.Equals(circle2)); // => False (Radius differs) +Console.WriteLine(circle1.Equals(circle3)); // => True +Console.WriteLine(circle1.Equals(shape1)); // => False (different runtime types) +// + +// ── Type declarations ──────────────────────────────────────────────────────── + +// +// Shape is an unsealed base class. Making Equals virtual and guarding with GetType() +// ensures a derived instance is never equal to an instance of a different runtime type. +class Shape : IEquatable +{ + public string Color { get; } + public Shape(string color) => Color = color; + + public override bool Equals(object? obj) => Equals(obj as Shape); + + // virtual so derived classes can override and augment the comparison + public virtual bool Equals(Shape? other) => + other is not null && + GetType() == other.GetType() && // reject different runtime types + Color == other.Color; + + // GetType() is included because equality requires matching runtime types + public override int GetHashCode() => HashCode.Combine(GetType(), Color); + + public static bool operator ==(Shape? l, Shape? r) => l?.Equals(r) ?? r is null; + public static bool operator !=(Shape? l, Shape? r) => !(l == r); +} +// + +// +class Circle : Shape +{ + public double Radius { get; } + public Circle(string color, double radius) : base(color) => Radius = radius; + + public override bool Equals(object? obj) => Equals(obj as Shape); + + // Calls base.Equals to verify Color and runtime type, then adds Radius + public override bool Equals(Shape? other) => + other is Circle c && base.Equals(c) && Radius == c.Radius; + + public override int GetHashCode() => HashCode.Combine(GetType(), Color, Radius); +} +// + +// +class Color : IEquatable +{ + public Color(int r, int g, int b) + { + R = r; + G = g; + B = b; + } + + public int R { get; } + public int G { get; } + public int B { get; } + + public bool Equals(Color? other) => + other is not null && R == other.R && G == other.G && B == other.B; + + public override bool Equals(object? obj) => obj is Color other && Equals(other); + public override int GetHashCode() => HashCode.Combine(R, G, B); +} +// \ No newline at end of file diff --git a/docs/csharp/programming-guide/statements-expressions-operators/equality-comparisons.md b/docs/csharp/programming-guide/statements-expressions-operators/equality-comparisons.md deleted file mode 100644 index 2f0c6a42cb514..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/equality-comparisons.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: "Equality Comparisons" -description: Learn about equality comparisons. See descriptions of 'value equality' and 'reference equality', and view additional resources. -ms.date: 07/20/2015 -helpviewer_keywords: - - "object equality [C#]" -ms.assetid: 10b865ea-4e7b-4127-9242-c9b8f57d9f04 ---- -# Equality comparisons (C# Programming Guide) - -It is sometimes necessary to compare two values for equality. In some cases, you are testing for *value equality*, also known as *equivalence*, which means that the values that are contained by the two variables are equal. In other cases, you have to determine whether two variables refer to the same underlying object in memory. This type of equality is called *reference equality*, or *identity*. This topic describes these two kinds of equality and provides links to other topics for more information. - -## Reference equality - - Reference equality means that two object references refer to the same underlying object. This can occur through simple assignment, as shown in the following example. - - [!code-csharp[csProgGuideStatements#18](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs#18)] - - In this code, two objects are created, but after the assignment statement, both references refer to the same object. Therefore they have reference equality. Use the method to determine whether two references refer to the same object. - -The concept of reference equality applies only to reference types. Value type objects cannot have reference equality because when an instance of a value type is assigned to a variable, a copy of the value is made. Therefore you can never have two unboxed structs that refer to the same location in memory. Furthermore, if you use to compare two value types, the result will always be `false`, even if the values that are contained in the objects are all identical. This is because each variable is boxed into a separate object instance. For more information, see [How to test for reference equality (Identity)](./how-to-test-for-reference-equality-identity.md). - -## Value equality - - Value equality means that two objects contain the same value or values. For primitive value types such as [int](../../language-reference/builtin-types/integral-numeric-types.md) or [bool](../../language-reference/builtin-types/bool.md), tests for value equality are straightforward. You can use the [==](../../language-reference/operators/equality-operators.md#equality-operator-) operator, as shown in the following example. - -```csharp -int a = GetOriginalValue(); -int b = GetCurrentValue(); - -// Test for value equality. -if (b == a) -{ - // The two integers are equal. -} -``` - - For most other types, testing for value equality is more complex because it requires that you understand how the type defines it. For classes and structs that have multiple fields or properties, value equality is often defined to mean that all fields or properties have the same value. For example, two `Point` objects might be defined to be equivalent if pointA.X is equal to pointB.X and pointA.Y is equal to pointB.Y. For records, value equality means that two variables of a record type are equal if the types match and all property and field values match. - -However, there is no requirement that equivalence be based on all the fields in a type. It can be based on a subset. When you compare types that you do not own, you should make sure to understand specifically how equivalence is defined for that type. For more information about how to define value equality in your own classes and structs, see [How to define value equality for a type](./how-to-define-value-equality-for-a-type.md). - -### Value equality for floating-point values - - Equality comparisons of floating-point values ([double](../../language-reference/builtin-types/floating-point-numeric-types.md) and [float](../../language-reference/builtin-types/floating-point-numeric-types.md)) are problematic because of the imprecision of floating-point arithmetic on binary computers. For more information, see the remarks in the topic . - -## Related topics - -|Title|Description| -|-----------|-----------------| -|[How to test for reference equality (Identity)](./how-to-test-for-reference-equality-identity.md)|Describes how to determine whether two variables have reference equality.| -|[How to define value equality for a type](./how-to-define-value-equality-for-a-type.md)|Describes how to provide a custom definition of value equality for a type.| -|[Types](../../fundamentals/types/index.md)|Provides information about the C# type system and links to additional information.| -|[Records](../../fundamentals/types/records.md)|Provides information about record types, which test for value equality by default.| diff --git a/docs/csharp/programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md b/docs/csharp/programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md deleted file mode 100644 index c81749a2866d2..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md +++ /dev/null @@ -1,214 +0,0 @@ ---- -title: "How to define value equality for a class or struct" -description: Learn how to define value equality for a class or struct. See code examples and view available resources. -ms.topic: how-to -ms.date: 03/26/2021 -ai-usage: ai-assisted -helpviewer_keywords: - - "overriding Equals method [C#]" - - "object equivalence [C#]" - - "Equals method [C#], overriding" - - "value equality [C#]" - - "equivalence [C#]" -ms.assetid: 4084581e-b931-498b-9534-cf7ef5b68690 ---- -# How to define value equality for a class or struct (C# Programming Guide) - -> [!TIP] -> **Consider using [records](../../fundamentals/types/records.md) first.** Records automatically implement value equality with minimal code, making them the recommended approach for most data-focused types. If you need custom value equality logic or cannot use records, continue with the manual implementation steps below. - -When you define a class or struct, you decide whether it makes sense to create a custom definition of value equality (or equivalence) for the type. Typically, you implement value equality when you expect to add objects of the type to a collection, or when their primary purpose is to store a set of fields or properties. You can base your definition of value equality on a comparison of all the fields and properties in the type, or you can base the definition on a subset. - -In either case, and in both classes and structs, your implementation should follow the five guarantees of equivalence (for the following rules, assume that `x`, `y` and `z` are not null): - -1. The reflexive property: `x.Equals(x)` returns `true`. - -2. The symmetric property: `x.Equals(y)` returns the same value as `y.Equals(x)`. - -3. The transitive property: if `(x.Equals(y) && y.Equals(z))` returns `true`, then `x.Equals(z)` returns `true`. - -4. Successive invocations of `x.Equals(y)` return the same value as long as the objects referenced by x and y aren't modified. - -5. Any non-null value isn't equal to null. However, `x.Equals(y)` throws an exception when `x` is null. That breaks rules 1 or 2, depending on the argument to `Equals`. - -Any struct that you define already has a default implementation of value equality that it inherits from the override of the method. This implementation uses reflection to examine all the fields and properties in the type. Although this implementation produces correct results, it is relatively slow compared to a custom implementation that you write specifically for the type. - -The implementation details for value equality are different for classes and structs. However, both classes and structs require the same basic steps for implementing equality: - -1. **Override the [virtual](../../language-reference/keywords/virtual.md) method.** This provides polymorphic equality behavior, allowing your objects to be compared correctly when treated as `object` references. It ensures proper behavior in collections and when using polymorphism. In most cases, your implementation of `bool Equals( object obj )` should just call into the type-specific `Equals` method that is the implementation of the interface. (See step 2.) - -2. **Implement the interface by providing a type-specific `Equals` method.** This provides type-safe equality checking without boxing, resulting in better performance. It also avoids unnecessary casting and enables compile-time type checking. This is where the actual equivalence comparison is performed. For example, you might decide to define equality by comparing only one or two fields in your type. Don't throw exceptions from `Equals`. For classes that are related by inheritance: - - * This method should examine only fields that are declared in the class. It should call `base.Equals` to examine fields that are in the base class. (Don't call `base.Equals` if the type inherits directly from , because the implementation of performs a reference equality check.) - - * Two variables should be deemed equal only if the run-time types of the variables being compared are the same. Also, make sure that the `IEquatable` implementation of the `Equals` method for the run-time type is used if the run-time and compile-time types of a variable are different. One strategy for making sure run-time types are always compared correctly is to implement `IEquatable` only in `sealed` classes. For more information, see the [class example](#class-example) later in this article. - -3. **Optional but recommended: Overload the [==](../../language-reference/operators/equality-operators.md#equality-operator-) and [!=](../../language-reference/operators/equality-operators.md#inequality-operator-) operators.** This provides consistent and intuitive syntax for equality comparisons, matching user expectations from built-in types. It ensures that `obj1 == obj2` and `obj1.Equals(obj2)` behave the same way. - -4. **Override so that two objects that have value equality produce the same hash code.** This is required for correct behavior in hash-based collections like `Dictionary` and `HashSet`. Objects that are equal must have equal hash codes, or these collections won't work correctly. - -5. **Optional: To support definitions for "greater than" or "less than," implement the interface for your type, and also overload the [<=](../../language-reference/operators/comparison-operators.md#less-than-or-equal-operator-) and [>=](../../language-reference/operators/comparison-operators.md#greater-than-or-equal-operator-) operators.** This enables sorting operations and provides a complete ordering relationship for your type, useful when adding objects to sorted collections or when sorting arrays or lists. - -## Record example - -The following example shows how records automatically implement value equality with minimal code. The first record `TwoDPoint` is a simple record type that automatically implements value equality. The second record `ThreeDPoint` demonstrates that records can be derived from other records and still maintain proper value equality behavior: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/ValueEqualityRecord/Program.cs"::: - -Records provide several advantages for value equality: - -- **Automatic implementation**: Records automatically implement and override , , and the `==`/`!=` operators. -- **Correct inheritance behavior**: Records implement `IEquatable` using virtual methods that check the runtime type of both operands, ensuring correct behavior in inheritance hierarchies and polymorphic scenarios. -- **Immutability by default**: Records encourage immutable design, which works well with value equality semantics. -- **Concise syntax**: Positional parameters provide a compact way to define data types. -- **Better performance**: The compiler-generated equality implementation is optimized and doesn't use reflection like the default struct implementation. - -Use records when your primary goal is to store data and you need value equality semantics. - -## Records with members that use reference equality - -When records contain members that use reference equality, the automatic value equality behavior of records doesn't work as expected. This applies to collections like , arrays, and other reference types that don't implement value-based equality (with the notable exception of , which does implement value equality). - -> [!IMPORTANT] -> While records provide excellent value equality for basic data types, they don't automatically solve value equality for members that use reference equality. If a record contains a , , or other reference types that don't implement value equality, two record instances with identical content in those members will still not be equal because the members use reference equality. -> -> ```csharp -> public record PersonWithHobbies(string Name, List Hobbies); -> -> var person1 = new PersonWithHobbies("Alice", new List { "Reading", "Swimming" }); -> var person2 = new PersonWithHobbies("Alice", new List { "Reading", "Swimming" }); -> -> Console.WriteLine(person1.Equals(person2)); // False - different List instances! -> ``` - -This is because records use the method of each member, and collection types typically use reference equality rather than comparing their contents. - -The following shows the problem: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/Program.cs" id="ProblemExample"::: - -Here's how this behaves when you run the code: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/Program.cs" id="ProblemDemonstration"::: - -### Solutions for records with reference-equality members - -- **Custom implementation**: Replace the compiler-generated equality with a hand-coded version that provides content-based comparison for reference-equality members. For collections, implement element-by-element comparison using or similar methods. - -- **Use value types where possible**: Consider if your data can be represented with value types or immutable structures that naturally support value equality, such as or . - -- **Use types with value-based equality**: For collections, consider using types that implement value-based equality or implement custom collection types that override to provide content-based comparison, such as or . - -- **Design with reference equality in mind**: Accept that some members will use reference equality and design your application logic accordingly, ensuring that you reuse the same instances when equality is important. - -Here's an example of implementing custom equality for records with collections: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/Program.cs" id="SolutionExample"::: - -This custom implementation works correctly: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/Program.cs" id="SolutionDemonstration"::: - -The same issue affects arrays and other collection types: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/Program.cs" id="OtherTypes"::: - -Arrays also use reference equality, producing the same unexpected results: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/Program.cs" id="ArrayExample"::: - -Even readonly collections exhibit this reference equality behavior: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/Program.cs" id="ImmutableExample"::: - -The key insight is that records solve the *structural* equality problem but don't change the *semantic* equality behavior of the types they contain. - -## Class example - -The following example shows how to implement value equality in a class (reference type). This manual approach is needed when you can't use records or need custom equality logic: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/ValueEqualityClass/Program.cs"::: - -On classes (reference types), the default implementation of both methods performs a reference equality comparison, not a value equality check. When an implementer overrides the virtual method, the purpose is to give it value equality semantics. - -The `==` and `!=` operators can be used with classes even if the class does not overload them. However, the default behavior is to perform a reference equality check. In a class, if you overload the `Equals` method, you should overload the `==` and `!=` operators, but it is not required. - -> [!IMPORTANT] -> The preceding example code may not handle every inheritance scenario the way you expect. Consider the following code: -> -> ```csharp -> TwoDPoint p1 = new ThreeDPoint(1, 2, 3); -> TwoDPoint p2 = new ThreeDPoint(1, 2, 4); -> Console.WriteLine(p1.Equals(p2)); // output: True -> ``` -> -> This code reports that `p1` equals `p2` despite the difference in `z` values. The difference is ignored because the compiler picks the `TwoDPoint` implementation of `IEquatable` based on the compile-time type. This is a fundamental issue with polymorphic equality in inheritance hierarchies. - -## Polymorphic equality - -When implementing value equality in inheritance hierarchies with classes, the standard approach shown in the class example can lead to incorrect behavior when objects are used polymorphically. The issue occurs because implementations are chosen based on compile-time type, not runtime type. - -### The problem with standard implementations - -Consider this problematic scenario: - -```csharp -TwoDPoint p1 = new ThreeDPoint(1, 2, 3); // Declared as TwoDPoint -TwoDPoint p2 = new ThreeDPoint(1, 2, 4); // Declared as TwoDPoint -Console.WriteLine(p1.Equals(p2)); // True - but should be False! -``` - -The comparison returns `True` because the compiler selects `TwoDPoint.Equals(TwoDPoint)` based on the declared type, ignoring the `Z` coordinate differences. - -The key to correct polymorphic equality is ensuring that all equality comparisons use the virtual method, which can check runtime types and handle inheritance correctly. This can be achieved by using explicit interface implementation for that delegates to the virtual method: - -The base class demonstrates the key patterns: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/ValueEqualityPolymorphic/Program.cs" id="TwoDPointClass"::: - -The derived class correctly extends the equality logic: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/ValueEqualityPolymorphic/Program.cs" id="ThreeDPointClass"::: - -Here's how this implementation handles the problematic polymorphic scenarios: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/ValueEqualityPolymorphic/Program.cs" id="PolymorphicTest"::: - -The implementation also correctly handles direct type comparisons: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/ValueEqualityPolymorphic/Program.cs" id="DirectTest"::: - -The equality implementation also works properly with collections: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/ValueEqualityPolymorphic/Program.cs" id="CollectionTest"::: - -The preceding code demonstrates key elements to implementing value based equality: - -- **Virtual `Equals(object?)` override**: The main equality logic happens in the virtual method, which is called regardless of compile-time type. -- **Runtime type checking**: Using `this.GetType() != p.GetType()` ensures that objects of different types are never considered equal. -- **Explicit interface implementation**: The implementation delegates to the virtual method, preventing compile-time type selection issues. -- **Protected virtual helper method**: The `protected virtual Equals(TwoDPoint? p)` method allows derived classes to override equality logic while maintaining type safety. - -Use this pattern when: - -- You have inheritance hierarchies where value equality is important -- Objects might be used polymorphically (declared as base type, instantiated as derived type) -- You need reference types with value equality semantics - -The preferred approach is to use `record` types to implement value based equality. This approach requires a more complex implementation than the standard approach and requires thorough testing of polymorphic scenarios to ensure correctness. - -## Struct example - -The following example shows how to implement value equality in a struct (value type). While structs have default value equality, a custom implementation can improve performance: - -:::code language="csharp" source="snippets/how-to-define-value-equality-for-a-type/ValueEqualityStruct/Program.cs"::: - -For structs, the default implementation of (which is the overridden version in ) performs a value equality check by using reflection to compare the values of every field in the type. Although this implementation produces correct results, it is relatively slow compared to a custom implementation that you write specifically for the type. - -When you override the virtual `Equals` method in a struct, the purpose is to provide a more efficient means of performing the value equality check and optionally to base the comparison on some subset of the struct's fields or properties. - -The [==](../../language-reference/operators/equality-operators.md#equality-operator-) and [!=](../../language-reference/operators/equality-operators.md#inequality-operator-) operators can't operate on a struct unless the struct explicitly overloads them. - -## See also - -- [Equality comparisons](equality-comparisons.md) diff --git a/docs/csharp/programming-guide/statements-expressions-operators/how-to-test-for-reference-equality-identity.md b/docs/csharp/programming-guide/statements-expressions-operators/how-to-test-for-reference-equality-identity.md deleted file mode 100644 index 45cd03178ccd6..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/how-to-test-for-reference-equality-identity.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: "How to test for reference equality (Identity)" -description: Learn how to test for reference equality (Identity). See a code example and view additional available resources. -ms.date: 07/20/2015 -ms.topic: how-to -helpviewer_keywords: - - "object identity [C#]" - - "reference equality [C#]" -ms.assetid: 91307fda-267b-4fd2-a338-2aada39ee791 ---- -# How to test for reference equality (Identity) (C# Programming Guide) - -You do not have to implement any custom logic to support reference equality comparisons in your types. This functionality is provided for all types by the static method. - - The following example shows how to determine whether two variables have *reference equality*, which means that they refer to the same object in memory. - -The example also shows why always returns `false` for value types. This is due to **boxing**, which creates separate object instances for each value type argument. Additionally, you should not use to determine string equality. - -## Example - - [!code-csharp[TestingReferenceEquality](snippets/how-to-test-for-reference-equality-identity/Program.cs)] - - The implementation of `Equals` in the universal base class also performs a reference equality check, but it is best not to use this because, if a class happens to override the method, the results might not be what you expect. The same is true for the `==` and `!=` operators. When they are operating on reference types, the default behavior of `==` and `!=` is to perform a reference equality check. However, derived classes can overload the operator to perform a value equality check. To minimize the potential for error, it is best to always use when you have to determine whether two objects have reference equality. - - Constant strings within the same assembly are always interned by the runtime. That is, only one instance of each unique literal string is maintained. However, the runtime does not guarantee that strings created at run time are interned, nor does it guarantee that two equal constant strings in different assemblies are interned. - -> [!NOTE] -> `ReferenceEquals` returns `false` for value types due to **boxing**, as each argument is independently boxed into a separate object. - -## See also - -- [Equality Comparisons](./equality-comparisons.md) diff --git a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/Program.cs b/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/Program.cs deleted file mode 100644 index 7c0639eb0a90d..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/RecordCollectionsIssue/Program.cs +++ /dev/null @@ -1,144 +0,0 @@ -namespace RecordCollectionsIssue; - -// -// Records with reference-equality members don't work as expected -public record PersonWithHobbies(string Name, List Hobbies); -// - -// -// A potential solution using IEquatable with custom equality -public record PersonWithHobbiesFixed(string Name, List Hobbies) : IEquatable -{ - public virtual bool Equals(PersonWithHobbiesFixed? other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - - // Use SequenceEqual for List comparison - return Name == other.Name && Hobbies.SequenceEqual(other.Hobbies); - } - - public override int GetHashCode() - { - // Create hash based on content, not reference - var hashCode = new HashCode(); - hashCode.Add(Name); - foreach (var hobby in Hobbies) - { - hashCode.Add(hobby); - } - return hashCode.ToHashCode(); - } -} -// - -// -// These also use reference equality - the issue persists -public record PersonWithHobbiesArray(string Name, string[] Hobbies); - -public record PersonWithHobbiesImmutable(string Name, IReadOnlyList Hobbies); -// - -// -class Program -{ - static void Main(string[] args) - { - // - Console.WriteLine("=== Records with Collections - The Problem ==="); - - // Problem: Records with mutable collections use reference equality for the collection - var person1 = new PersonWithHobbies("Alice", [ "Reading", "Swimming" ]); - var person2 = new PersonWithHobbies("Alice", [ "Reading", "Swimming" ]); - - Console.WriteLine($"person1: {person1}"); - Console.WriteLine($"person2: {person2}"); - Console.WriteLine($"person1.Equals(person2): {person1.Equals(person2)}"); // False! Different List instances - Console.WriteLine($"Lists have same content: {person1.Hobbies.SequenceEqual(person2.Hobbies)}"); // True - Console.WriteLine(); - // - - // - Console.WriteLine("=== Solution 1: Custom IEquatable Implementation ==="); - - var personFixed1 = new PersonWithHobbiesFixed("Bob", [ "Cooking", "Hiking" ]); - var personFixed2 = new PersonWithHobbiesFixed("Bob", [ "Cooking", "Hiking" ]); - - Console.WriteLine($"personFixed1: {personFixed1}"); - Console.WriteLine($"personFixed2: {personFixed2}"); - Console.WriteLine($"personFixed1.Equals(personFixed2): {personFixed1.Equals(personFixed2)}"); // True! Custom equality - Console.WriteLine(); - // - - // - Console.WriteLine("=== Arrays Also Use Reference Equality ==="); - - var personArray1 = new PersonWithHobbiesArray("Charlie", ["Gaming", "Music" ]); - var personArray2 = new PersonWithHobbiesArray("Charlie", ["Gaming", "Music" ]); - - Console.WriteLine($"personArray1: {personArray1}"); - Console.WriteLine($"personArray2: {personArray2}"); - Console.WriteLine($"personArray1.Equals(personArray2): {personArray1.Equals(personArray2)}"); // False! Arrays use reference equality too - Console.WriteLine($"Arrays have same content: {personArray1.Hobbies.SequenceEqual(personArray2.Hobbies)}"); // True - Console.WriteLine(); - // - - // - Console.WriteLine("=== Same Issue with IReadOnlyList ==="); - - var personImmutable1 = new PersonWithHobbiesImmutable("Diana", [ "Art", "Travel" ]); - var personImmutable2 = new PersonWithHobbiesImmutable("Diana", [ "Art", "Travel" ]); - - Console.WriteLine($"personImmutable1: {personImmutable1}"); - Console.WriteLine($"personImmutable2: {personImmutable2}"); - Console.WriteLine($"personImmutable1.Equals(personImmutable2): {personImmutable1.Equals(personImmutable2)}"); // False! Reference equality - Console.WriteLine($"Content is the same: {personImmutable1.Hobbies.SequenceEqual(personImmutable2.Hobbies)}"); // True - Console.WriteLine(); - // - - Console.WriteLine("=== Collection Behavior Summary ==="); - Console.WriteLine("Type | Equals Result | Reason"); - Console.WriteLine("----------------------------------|---------------|------------------"); - Console.WriteLine($"Record with List | {person1.Equals(person2),-13} | Reference equality"); - Console.WriteLine($"Record with custom IEquatable | {personFixed1.Equals(personFixed2),-13} | Custom equality logic"); - Console.WriteLine($"Record with Array | {personArray1.Equals(personArray2),-13} | Reference equality"); - Console.WriteLine($"Record with IReadOnlyList | {personImmutable1.Equals(personImmutable2),-13} | Reference equality"); - - Console.WriteLine("\nPress any key to exit."); - Console.ReadKey(); - } -} -// - -/* Expected Output: -=== Records with Collections - The Problem === -person1: PersonWithHobbies { Name = Alice, Hobbies = System.Collections.Generic.List`1[System.String] } -person2: PersonWithHobbies { Name = Alice, Hobbies = System.Collections.Generic.List`1[System.String] } -person1.Equals(person2): False -Lists have same content: True - -=== Solution 1: Custom IEquatable Implementation === -personFixed1: PersonWithHobbiesFixed { Name = Bob, Hobbies = System.Collections.Generic.List`1[System.String] } -personFixed2: PersonWithHobbiesFixed { Name = Bob, Hobbies = System.Collections.Generic.List`1[System.String] } -personFixed1.Equals(personFixed2): True - -=== Arrays Also Use Reference Equality === -personArray1: PersonWithHobbiesArray { Name = Charlie, Hobbies = System.String[] } -personArray2: PersonWithHobbiesArray { Name = Charlie, Hobbies = System.String[] } -personArray1.Equals(personArray2): False -Arrays have same content: True - -=== Same Issue with IReadOnlyList === -personImmutable1: PersonWithHobbiesImmutable { Name = Diana, Hobbies = System.String[] } -personImmutable2: PersonWithHobbiesImmutable { Name = Diana, Hobbies = System.String[] } -personImmutable1.Equals(personImmutable2): False -Content is the same: True - -=== Collection Behavior Summary === -Type | Equals Result | Reason -----------------------------------|---------------|------------------ -Record with List | False | Reference equality -Record with custom IEquatable | True | Custom equality logic -Record with Array | False | Reference equality -Record with IReadOnlyList | False | Reference equality -*/ \ No newline at end of file diff --git a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityClass/Program.cs b/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityClass/Program.cs deleted file mode 100644 index a9d497c3526ac..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityClass/Program.cs +++ /dev/null @@ -1,175 +0,0 @@ -namespace ValueEqualityClass; - -class TwoDPoint : IEquatable -{ - public int X { get; private set; } - public int Y { get; private set; } - - public TwoDPoint(int x, int y) - { - if (x is (< 1 or > 2000) || y is (< 1 or > 2000)) - { - throw new ArgumentException("Point must be in range 1 - 2000"); - } - this.X = x; - this.Y = y; - } - - public override bool Equals(object obj) => this.Equals(obj as TwoDPoint); - - public bool Equals(TwoDPoint p) - { - if (p is null) - { - return false; - } - - // Optimization for a common success case. - if (Object.ReferenceEquals(this, p)) - { - return true; - } - - // If run-time types are not exactly the same, return false. - if (this.GetType() != p.GetType()) - { - return false; - } - - // Return true if the fields match. - // Note that the base class is not invoked because it is - // System.Object, which defines Equals as reference equality. - return (X == p.X) && (Y == p.Y); - } - - public override int GetHashCode() => (X, Y).GetHashCode(); - - public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs) - { - if (lhs is null) - { - if (rhs is null) - { - return true; - } - - // Only the left side is null. - return false; - } - // Equals handles case of null on right side. - return lhs.Equals(rhs); - } - - public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs) => !(lhs == rhs); -} - -// For the sake of simplicity, assume a ThreeDPoint IS a TwoDPoint. -class ThreeDPoint : TwoDPoint, IEquatable -{ - public int Z { get; private set; } - - public ThreeDPoint(int x, int y, int z) - : base(x, y) - { - if ((z < 1) || (z > 2000)) - { - throw new ArgumentException("Point must be in range 1 - 2000"); - } - this.Z = z; - } - - public override bool Equals(object obj) => this.Equals(obj as ThreeDPoint); - - public bool Equals(ThreeDPoint p) - { - if (p is null) - { - return false; - } - - // Optimization for a common success case. - if (Object.ReferenceEquals(this, p)) - { - return true; - } - - // Check properties that this class declares. - if (Z == p.Z) - { - // Let base class check its own fields - // and do the run-time type comparison. - return base.Equals((TwoDPoint)p); - } - else - { - return false; - } - } - - public override int GetHashCode() => (X, Y, Z).GetHashCode(); - - public static bool operator ==(ThreeDPoint lhs, ThreeDPoint rhs) - { - if (lhs is null) - { - if (rhs is null) - { - // null == null = true. - return true; - } - - // Only the left side is null. - return false; - } - // Equals handles the case of null on right side. - return lhs.Equals(rhs); - } - - public static bool operator !=(ThreeDPoint lhs, ThreeDPoint rhs) => !(lhs == rhs); -} - -class Program -{ - static void Main(string[] args) - { - ThreeDPoint pointA = new ThreeDPoint(3, 4, 5); - ThreeDPoint pointB = new ThreeDPoint(3, 4, 5); - ThreeDPoint pointC = null; - int i = 5; - - Console.WriteLine($"pointA.Equals(pointB) = {pointA.Equals(pointB)}"); - Console.WriteLine($"pointA == pointB = {pointA == pointB}"); - Console.WriteLine($"null comparison = {pointA.Equals(pointC)}"); - Console.WriteLine($"Compare to some other type = {pointA.Equals(i)}"); - - TwoDPoint pointD = null; - TwoDPoint pointE = null; - - Console.WriteLine($"Two null TwoDPoints are equal: {pointD == pointE}"); - - pointE = new TwoDPoint(3, 4); - Console.WriteLine($"(pointE == pointA) = {pointE == pointA}"); - Console.WriteLine($"(pointA == pointE) = {pointA == pointE}"); - Console.WriteLine($"(pointA != pointE) = {pointA != pointE}"); - - System.Collections.ArrayList list = new System.Collections.ArrayList(); - list.Add(new ThreeDPoint(3, 4, 5)); - Console.WriteLine($"pointE.Equals(list[0]): {pointE.Equals(list[0])}"); - - // Keep the console window open in debug mode. - Console.WriteLine("Press any key to exit."); - Console.ReadKey(); - } -} - -/* Output: - pointA.Equals(pointB) = True - pointA == pointB = True - null comparison = False - Compare to some other type = False - Two null TwoDPoints are equal: True - (pointE == pointA) = False - (pointA == pointE) = False - (pointA != pointE) = True - pointE.Equals(list[0]): False -*/ diff --git a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityClass/ValueEqualityClass.csproj b/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityClass/ValueEqualityClass.csproj deleted file mode 100644 index f704bf4988fa6..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityClass/ValueEqualityClass.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - diff --git a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityPolymorphic/Program.cs b/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityPolymorphic/Program.cs deleted file mode 100644 index 8ef4dc9eb9358..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityPolymorphic/Program.cs +++ /dev/null @@ -1,247 +0,0 @@ -namespace ValueEqualityPolymorphic; - -// -// Safe polymorphic equality implementation using explicit interface implementation -class TwoDPoint : IEquatable -{ - public int X { get; private set; } - public int Y { get; private set; } - - public TwoDPoint(int x, int y) - { - if (x is (< 1 or > 2000) || y is (< 1 or > 2000)) - { - throw new ArgumentException("Point must be in range 1 - 2000"); - } - this.X = x; - this.Y = y; - } - - public override bool Equals(object? obj) => Equals(obj as TwoDPoint); - - // Explicit interface implementation prevents compile-time type issues - bool IEquatable.Equals(TwoDPoint? p) => Equals((object?)p); - - protected virtual bool Equals(TwoDPoint? p) - { - if (p is null) - { - return false; - } - - // Optimization for a common success case. - if (Object.ReferenceEquals(this, p)) - { - return true; - } - - // If run-time types are not exactly the same, return false. - if (this.GetType() != p.GetType()) - { - return false; - } - - // Return true if the fields match. - // Note that the base class is not invoked because it is - // System.Object, which defines Equals as reference equality. - return (X == p.X) && (Y == p.Y); - } - - public override int GetHashCode() => (X, Y).GetHashCode(); - - public static bool operator ==(TwoDPoint? lhs, TwoDPoint? rhs) - { - if (lhs is null) - { - if (rhs is null) - { - return true; - } - - // Only the left side is null. - return false; - } - // Equals handles case of null on right side. - return lhs.Equals(rhs); - } - - public static bool operator !=(TwoDPoint? lhs, TwoDPoint? rhs) => !(lhs == rhs); -} -// - -// -// For the sake of simplicity, assume a ThreeDPoint IS a TwoDPoint. -class ThreeDPoint : TwoDPoint, IEquatable -{ - public int Z { get; private set; } - - public ThreeDPoint(int x, int y, int z) - : base(x, y) - { - if ((z < 1) || (z > 2000)) - { - throw new ArgumentException("Point must be in range 1 - 2000"); - } - this.Z = z; - } - - public override bool Equals(object? obj) => Equals(obj as ThreeDPoint); - - // Explicit interface implementation prevents compile-time type issues - bool IEquatable.Equals(ThreeDPoint? p) => Equals((object?)p); - - protected override bool Equals(TwoDPoint? p) - { - if (p is null) - { - return false; - } - - // Optimization for a common success case. - if (Object.ReferenceEquals(this, p)) - { - return true; - } - - // Runtime type check happens in the base method - if (p is ThreeDPoint threeD) - { - // Check properties that this class declares. - if (Z != threeD.Z) - { - return false; - } - - return base.Equals(p); - } - - return false; - } - - public override int GetHashCode() => (X, Y, Z).GetHashCode(); - - public static bool operator ==(ThreeDPoint? lhs, ThreeDPoint? rhs) - { - if (lhs is null) - { - if (rhs is null) - { - // null == null = true. - return true; - } - - // Only the left side is null. - return false; - } - // Equals handles the case of null on right side. - return lhs.Equals(rhs); - } - - public static bool operator !=(ThreeDPoint? lhs, ThreeDPoint? rhs) => !(lhs == rhs); -} -// - -// -class Program -{ - static void Main(string[] args) - { - // - Console.WriteLine("=== Safe Polymorphic Equality ==="); - - // Test polymorphic scenarios that were problematic before - TwoDPoint p1 = new ThreeDPoint(1, 2, 3); - TwoDPoint p2 = new ThreeDPoint(1, 2, 4); - TwoDPoint p3 = new ThreeDPoint(1, 2, 3); - TwoDPoint p4 = new TwoDPoint(1, 2); - - Console.WriteLine("Testing polymorphic equality (declared as TwoDPoint):"); - Console.WriteLine($"p1 = ThreeDPoint(1, 2, 3) as TwoDPoint"); - Console.WriteLine($"p2 = ThreeDPoint(1, 2, 4) as TwoDPoint"); - Console.WriteLine($"p3 = ThreeDPoint(1, 2, 3) as TwoDPoint"); - Console.WriteLine($"p4 = TwoDPoint(1, 2)"); - Console.WriteLine(); - - Console.WriteLine($"p1.Equals(p2) = {p1.Equals(p2)}"); // False - different Z values - Console.WriteLine($"p1.Equals(p3) = {p1.Equals(p3)}"); // True - same values - Console.WriteLine($"p1.Equals(p4) = {p1.Equals(p4)}"); // False - different types - Console.WriteLine($"p4.Equals(p1) = {p4.Equals(p1)}"); // False - different types - Console.WriteLine(); - // - - // - // Test direct type comparisons - var point3D_A = new ThreeDPoint(3, 4, 5); - var point3D_B = new ThreeDPoint(3, 4, 5); - var point3D_C = new ThreeDPoint(3, 4, 7); - var point2D_A = new TwoDPoint(3, 4); - - Console.WriteLine("Testing direct type comparisons:"); - Console.WriteLine($"point3D_A.Equals(point3D_B) = {point3D_A.Equals(point3D_B)}"); // True - Console.WriteLine($"point3D_A.Equals(point3D_C) = {point3D_A.Equals(point3D_C)}"); // False - Console.WriteLine($"point3D_A.Equals(point2D_A) = {point3D_A.Equals(point2D_A)}"); // False - Console.WriteLine($"point2D_A.Equals(point3D_A) = {point2D_A.Equals(point3D_A)}"); // False - Console.WriteLine(); - // - - // - // Test operators - Console.WriteLine("Testing operators:"); - Console.WriteLine($"p1 == p2: {p1 == p2}"); // False - Console.WriteLine($"p1 == p3: {p1 == p3}"); // True - Console.WriteLine($"point3D_A == point3D_B: {point3D_A == point3D_B}"); // True - Console.WriteLine(); - // - - // - // Test with collections - Console.WriteLine("Testing with collections:"); - var hashSet = new HashSet { p1, p2, p3, p4 }; - Console.WriteLine($"HashSet contains {hashSet.Count} unique points"); // Should be 3: one ThreeDPoint(1,2,3), one ThreeDPoint(1,2,4), one TwoDPoint(1,2) - - var dictionary = new Dictionary - { - { p1, "First 3D point" }, - { p2, "Second 3D point" }, - { p4, "2D point" } - }; - - Console.WriteLine($"Dictionary contains {dictionary.Count} entries"); - Console.WriteLine($"Dictionary lookup for equivalent point: {dictionary.ContainsKey(new ThreeDPoint(1, 2, 3))}"); // True - // - - Console.WriteLine("Press any key to exit."); - Console.ReadKey(); - } -} -// - -/* Expected Output: -=== Safe Polymorphic Equality === -Testing polymorphic equality (declared as TwoDPoint): -p1 = ThreeDPoint(1, 2, 3) as TwoDPoint -p2 = ThreeDPoint(1, 2, 4) as TwoDPoint -p3 = ThreeDPoint(1, 2, 3) as TwoDPoint -p4 = TwoDPoint(1, 2) - -p1.Equals(p2) = False -p1.Equals(p3) = True -p1.Equals(p4) = False -p4.Equals(p1) = False - -Testing direct type comparisons: -point3D_A.Equals(point3D_B) = True -point3D_A.Equals(point3D_C) = False -point3D_A.Equals(point2D_A) = False -point2D_A.Equals(point3D_A) = False - -Testing operators: -p1 == p2: False -p1 == p3: True -point3D_A == point3D_B: True - -Testing with collections: -HashSet contains 3 unique points -Dictionary contains 3 entries -Dictionary lookup for equivalent point: True -*/ \ No newline at end of file diff --git a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityPolymorphic/ValueEqualityPolymorphic.csproj b/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityPolymorphic/ValueEqualityPolymorphic.csproj deleted file mode 100644 index fd4dd4565750e..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityPolymorphic/ValueEqualityPolymorphic.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - \ No newline at end of file diff --git a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityRecord/Program.cs b/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityRecord/Program.cs deleted file mode 100644 index f9041b9ce482d..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityRecord/Program.cs +++ /dev/null @@ -1,99 +0,0 @@ -namespace ValueEqualityRecord; - -public record TwoDPoint(int X, int Y); - -public record ThreeDPoint(int X, int Y, int Z) : TwoDPoint(X, Y); - -class Program -{ - static void Main(string[] args) - { - // Create some points - TwoDPoint pointA = new TwoDPoint(3, 4); - TwoDPoint pointB = new TwoDPoint(3, 4); - TwoDPoint pointC = new TwoDPoint(5, 6); - - ThreeDPoint point3D_A = new ThreeDPoint(3, 4, 5); - ThreeDPoint point3D_B = new ThreeDPoint(3, 4, 5); - ThreeDPoint point3D_C = new ThreeDPoint(3, 4, 7); - - Console.WriteLine("=== Value Equality with Records ==="); - - // Value equality works automatically - Console.WriteLine($"pointA.Equals(pointB) = {pointA.Equals(pointB)}"); // True - Console.WriteLine($"pointA == pointB = {pointA == pointB}"); // True - Console.WriteLine($"pointA.Equals(pointC) = {pointA.Equals(pointC)}"); // False - Console.WriteLine($"pointA == pointC = {pointA == pointC}"); // False - - Console.WriteLine("\n=== Hash Codes ==="); - - // Equal objects have equal hash codes automatically - Console.WriteLine($"pointA.GetHashCode() = {pointA.GetHashCode()}"); - Console.WriteLine($"pointB.GetHashCode() = {pointB.GetHashCode()}"); - Console.WriteLine($"pointC.GetHashCode() = {pointC.GetHashCode()}"); - - Console.WriteLine("\n=== Inheritance with Records ==="); - - // Inheritance works correctly with value equality - Console.WriteLine($"point3D_A.Equals(point3D_B) = {point3D_A.Equals(point3D_B)}"); // True - Console.WriteLine($"point3D_A == point3D_B = {point3D_A == point3D_B}"); // True - Console.WriteLine($"point3D_A.Equals(point3D_C) = {point3D_A.Equals(point3D_C)}"); // False - - // Different types are not equal (unlike problematic class example) - Console.WriteLine($"pointA.Equals(point3D_A) = {pointA.Equals(point3D_A)}"); // False - - Console.WriteLine("\n=== Collections ==="); - - // Works seamlessly with collections - var pointSet = new HashSet { pointA, pointB, pointC }; - Console.WriteLine($"Set contains {pointSet.Count} unique points"); // 2 unique points - - var pointDict = new Dictionary - { - { pointA, "First point" }, - { pointC, "Different point" } - }; - - // Demonstrate that equivalent points work as the same key - var duplicatePoint = new TwoDPoint(3, 4); - Console.WriteLine($"Dictionary contains key for {duplicatePoint}: {pointDict.ContainsKey(duplicatePoint)}"); // True - Console.WriteLine($"Dictionary contains {pointDict.Count} entries"); // 2 entries - - Console.WriteLine("\n=== String Representation ==="); - - // Automatic ToString implementation - Console.WriteLine($"pointA.ToString() = {pointA}"); - Console.WriteLine($"point3D_A.ToString() = {point3D_A}"); - - Console.WriteLine("Press any key to exit."); - Console.ReadKey(); - } -} - -/* Expected Output: -=== Value Equality with Records === -pointA.Equals(pointB) = True -pointA == pointB = True -pointA.Equals(pointC) = False -pointA == pointC = False - -=== Hash Codes === -pointA.GetHashCode() = -1400834708 -pointB.GetHashCode() = -1400834708 -pointC.GetHashCode() = -148136000 - -=== Inheritance with Records === -point3D_A.Equals(point3D_B) = True -point3D_A == point3D_B = True -point3D_A.Equals(point3D_C) = False -pointA.Equals(point3D_A) = False - -=== Collections === -Set contains 2 unique points -Dictionary contains key for TwoDPoint { X = 3, Y = 4 }: True -Dictionary contains 2 entries - -=== String Representation === -pointA.ToString() = TwoDPoint { X = 3, Y = 4 } -point3D_A.ToString() = ThreeDPoint { X = 3, Y = 4, Z = 5 } -*/ \ No newline at end of file diff --git a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityRecord/ValueEqualityRecord.csproj b/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityRecord/ValueEqualityRecord.csproj deleted file mode 100644 index fd4dd4565750e..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityRecord/ValueEqualityRecord.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - \ No newline at end of file diff --git a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityStruct/Program.cs b/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityStruct/Program.cs deleted file mode 100644 index aa4a81f1620a4..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-define-value-equality-for-a-type/ValueEqualityStruct/Program.cs +++ /dev/null @@ -1,97 +0,0 @@ -namespace ValueEqualityStruct -{ - struct TwoDPoint : IEquatable - { - public int X { get; private set; } - public int Y { get; private set; } - - public TwoDPoint(int x, int y) - : this() - { - if (x is (< 1 or > 2000) || y is (< 1 or > 2000)) - { - throw new ArgumentException("Point must be in range 1 - 2000"); - } - X = x; - Y = y; - } - - public override bool Equals(object? obj) => obj is TwoDPoint other && this.Equals(other); - - public bool Equals(TwoDPoint p) => X == p.X && Y == p.Y; - - public override int GetHashCode() => (X, Y).GetHashCode(); - - public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs) => lhs.Equals(rhs); - - public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs) => !(lhs == rhs); - } - - class Program - { - static void Main(string[] args) - { - TwoDPoint pointA = new TwoDPoint(3, 4); - TwoDPoint pointB = new TwoDPoint(3, 4); - int i = 5; - - // True: - Console.WriteLine($"pointA.Equals(pointB) = {pointA.Equals(pointB)}"); - // True: - Console.WriteLine($"pointA == pointB = {pointA == pointB}"); - // True: - Console.WriteLine($"object.Equals(pointA, pointB) = {object.Equals(pointA, pointB)}"); - // False: - Console.WriteLine($"pointA.Equals(null) = {pointA.Equals(null)}"); - // False: - Console.WriteLine($"(pointA == null) = {pointA == null}"); - // True: - Console.WriteLine($"(pointA != null) = {pointA != null}"); - // False: - Console.WriteLine($"pointA.Equals(i) = {pointA.Equals(i)}"); - // CS0019: - // Console.WriteLine($"pointA == i = {pointA == i}"); - - // Compare unboxed to boxed. - System.Collections.ArrayList list = new System.Collections.ArrayList(); - list.Add(new TwoDPoint(3, 4)); - // True: - Console.WriteLine($"pointA.Equals(list[0]): {pointA.Equals(list[0])}"); - - // Compare nullable to nullable and to non-nullable. - TwoDPoint? pointC = null; - TwoDPoint? pointD = null; - // False: - Console.WriteLine($"pointA == (pointC = null) = {pointA == pointC}"); - // True: - Console.WriteLine($"pointC == pointD = {pointC == pointD}"); - - TwoDPoint temp = new TwoDPoint(3, 4); - pointC = temp; - // True: - Console.WriteLine($"pointA == (pointC = 3,4) = {pointA == pointC}"); - - pointD = temp; - // True: - Console.WriteLine($"pointD == (pointC = 3,4) = {pointD == pointC}"); - - Console.WriteLine("Press any key to exit."); - Console.ReadKey(); - } - } - - /* Output: - pointA.Equals(pointB) = True - pointA == pointB = True - Object.Equals(pointA, pointB) = True - pointA.Equals(null) = False - (pointA == null) = False - (pointA != null) = True - pointA.Equals(i) = False - pointE.Equals(list[0]): True - pointA == (pointC = null) = False - pointC == pointD = True - pointA == (pointC = 3,4) = True - pointD == (pointC = 3,4) = True - */ -} diff --git a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-test-for-reference-equality-identity/Program.cs b/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-test-for-reference-equality-identity/Program.cs deleted file mode 100644 index 8d8bdcaf9118f..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-test-for-reference-equality-identity/Program.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System.Text; - -namespace TestReferenceEquality -{ - struct TestStruct - { - public int Num { get; private set; } - public string Name { get; private set; } - - public TestStruct(int i, string s) : this() - { - Num = i; - Name = s; - } - } - - class TestClass - { - public int Num { get; set; } - public string? Name { get; set; } - } - - class Program - { - static void Main() - { - // Demonstrate reference equality with reference types. - #region ReferenceTypes - - // Create two reference type instances that have identical values. - TestClass tcA = new TestClass() { Num = 1, Name = "New TestClass" }; - TestClass tcB = new TestClass() { Num = 1, Name = "New TestClass" }; - - Console.WriteLine($"ReferenceEquals(tcA, tcB) = {Object.ReferenceEquals(tcA, tcB)}"); // false - - // After assignment, tcB and tcA refer to the same object. - // They now have reference equality. - tcB = tcA; - Console.WriteLine($"After assignment: ReferenceEquals(tcA, tcB) = {Object.ReferenceEquals(tcA, tcB)}"); // true - - // Changes made to tcA are reflected in tcB. Therefore, objects - // that have reference equality also have value equality. - tcA.Num = 42; - tcA.Name = "TestClass 42"; - Console.WriteLine($"tcB.Name = {tcB.Name} tcB.Num: {tcB.Num}"); - #endregion - - // Demonstrate that two value type instances never have reference equality. - #region ValueTypes - - TestStruct tsC = new TestStruct( 1, "TestStruct 1"); - - // Value types are boxed into separate objects when passed to ReferenceEquals. - // Even if the same variable is used twice, boxing ensures they are different instances. - TestStruct tsD = tsC; - Console.WriteLine($"After assignment: ReferenceEquals(tsC, tsD) = {Object.ReferenceEquals(tsC, tsD)}"); // false - #endregion - - #region stringRefEquality - // Constant strings within the same assembly are always interned by the runtime. - // This means they are stored in the same location in memory. Therefore, - // the two strings have reference equality although no assignment takes place. - string strA = "Hello world!"; - string strB = "Hello world!"; - Console.WriteLine($"ReferenceEquals(strA, strB) = {Object.ReferenceEquals(strA, strB)}"); // true - - // After a new string is assigned to strA, strA and strB - // are no longer interned and no longer have reference equality. - strA = "Goodbye world!"; - Console.WriteLine($"strA = '{strA}' strB = '{strB}'"); - - Console.WriteLine("After strA changes, ReferenceEquals(strA, strB) = {0}", - Object.ReferenceEquals(strA, strB)); // false - - // A string that is created at runtime cannot be interned. - StringBuilder sb = new StringBuilder("Hello world!"); - string stringC = sb.ToString(); - // False: - Console.WriteLine($"ReferenceEquals(stringC, strB) = {Object.ReferenceEquals(stringC, strB)}"); - - // The string class overloads the == operator to perform an equality comparison. - Console.WriteLine($"stringC == strB = {stringC == strB}"); // true - - #endregion - - // Keep the console open in debug mode. - Console.WriteLine("Press any key to exit."); - Console.ReadKey(); - } - } -} - -/* Output: - ReferenceEquals(tcA, tcB) = False - After assignment: ReferenceEquals(tcA, tcB) = True - tcB.Name = TestClass 42 tcB.Num: 42 - After assignment: ReferenceEquals(tsC, tsD) = False - ReferenceEquals(strA, strB) = True - strA = "Goodbye world!" strB = "Hello world!" - After strA changes, ReferenceEquals(strA, strB) = False - ReferenceEquals(stringC, strB) = False - stringC == strB = True -*/ diff --git a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-test-for-reference-equality-identity/TestingReferenceEquality.csproj b/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-test-for-reference-equality-identity/TestingReferenceEquality.csproj deleted file mode 100644 index 116202dc2c2bd..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/snippets/how-to-test-for-reference-equality-identity/TestingReferenceEquality.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - Exe - net8.0 - enable - enable - TestingReferenceEquality - - - diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index 3e5ee8035ff78..194b6d7dd4156 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -119,6 +119,9 @@ items: href: fundamentals/expressions/index.md - name: Equality href: fundamentals/expressions/equality.md + - name: Operators + displayName: "+, -, *, /, %, unary +, unary -, !, ++, --, <, >, <=, >=, ==, !=, &&, ||, ?:, =, +=, -=, *=, /=, %=" + href: fundamentals/expressions/operators.md - name: Selection statements href: fundamentals/statements/selection.md - name: Iteration statements @@ -541,14 +544,6 @@ items: href: programming-guide/statements-expressions-operators/statements.md - name: Expression-bodied members href: programming-guide/statements-expressions-operators/expression-bodied-members.md - - name: Equality and equality comparisons - items: - - name: Equality comparisons - href: programming-guide/statements-expressions-operators/equality-comparisons.md - - name: "How to define value equality for a type" - href: programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md - - name: "How to test for reference equality (identity)" - href: programming-guide/statements-expressions-operators/how-to-test-for-reference-equality-identity.md - name: Types items: - name: Casting and Type Conversions From 7baf09978b0230651a041e8e4e5f1eea56463277 Mon Sep 17 00:00:00 2001 From: Steve Date: Fri, 21 Aug 2026 05:53:07 +0900 Subject: [PATCH 3/9] Mention devirtualization in the overall page (#55475) --- docs/core/whats-new/dotnet-11/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/whats-new/dotnet-11/overview.md b/docs/core/whats-new/dotnet-11/overview.md index c3422e530cf46..42a05a01aeea4 100644 --- a/docs/core/whats-new/dotnet-11/overview.md +++ b/docs/core/whats-new/dotnet-11/overview.md @@ -21,7 +21,7 @@ The .NET 11 runtime includes: - Runtime-native async (Runtime Async), which produces cleaner stack traces and lower overhead. Runtime Async no longer requires `true` for projects that target `net11.0`. The runtime libraries themselves are compiled with `runtime-async=on`. - Runtime Async performance improvements, including JIT compilation of a dedicated runtime-async version of synchronous task-returning methods, async continuations that opt out of `ExecutionContext` capture when no ambient state is in use, and tail-merged suspension points that reduce generated code size. - Runtime Async tiered compilation, task and value-task factory intrinsics, and implicit tailcall improvements that reduce warm-up allocations and speed up common `await` paths. -- JIT improvements for bounds check elimination, redundant checked context removal, switch expression folding, constant-folding `SequenceEqual`, and redundant branch elimination. There are also new Arm SVE2 intrinsics, improved hardware-intrinsic cost modeling, and a faster `Math.BigMul` on x64 that emits a single `MUL` instruction. +- JIT improvements for bounds check elimination, redundant checked context removal, devirtualization, switch expression folding, constant-folding `SequenceEqual`, and redundant branch elimination. There are also new Arm SVE2 intrinsics, improved hardware-intrinsic cost modeling, and a faster `Math.BigMul` on x64 that emits a single `MUL` instruction. - CoreCLR on WebAssembly now runs the libraries test suite end to end, and the runtime adds AVX-VNNI-512 hardware intrinsics for vectorized multiply-add workloads. - In-process crash report logging on mobile platforms that captures the managed stack trace and runtime state before the process exits. - NativeAOT faster interface dispatch using a shared dispatch helper, reducing binary size at call sites and improving throughput for interface-heavy workloads. From 39261b5e980637ce68a12000c7ac836e59ec1586 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 20 Aug 2026 18:09:52 -0400 Subject: [PATCH 4/9] Consolidate expression-form context restriction diagnostics (#55614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: consolidate expression-form context restriction diagnostics (CS8115, CS8185, CS8209, CS8310, CS8312) This article consolidates five compiler diagnostics related to invalid expression contexts: - CS8115: Throw expressions in restricted contexts - CS8185: Declaration expressions in restricted contexts - CS8209: Void-returning expression restrictions - CS8310: Operator binding for null/default/new - CS8312: Default literal target type requirements Content organized by remediation strategy. Codes removed from catch-all. CS8188 preserved in expression-tree-restrictions.md per issue guidance. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: expand expression-form diagnostics to eight codes with keyword context restrictions (CS0175, CS0186, CS1547) Consolidate three standalone diagnostic articles (CS0175, CS0186, CS1547) into expression-form-restrictions.md, broadening the article scope from 5 to 8 codes while maintaining thematic coherence: MERGED CODES: - CS0175: Use of keyword 'base' is not valid in this context - CS0186: Use of null is not valid in this context - CS1547: Keyword 'void' cannot be used in this context CHANGES: - Updated expression-form-restrictions.md with new 'Keyword and literal context restrictions' section containing substantive guidance from the three deleted standalone articles - Merged all unique remediation information and examples - Updated front matter f1_keywords and helpviewer_keywords (8 codes) - Updated master error list with all 8 codes and exact Roslyn messages - Updated TOC displayName with all 8 codes - Removed old TOC entries for CS0175, CS0186, CS1547 - Created three redirects from old paths to new destination anchors - Deleted three now-retired standalone files FOOTPRINT REDUCTION: 3 standalone files → 1 consolidated article VERIFICATION: - YAML front matter valid - All 8 codes present in f1_keywords/helpviewer_keywords - No trailing whitespace - Redirect JSON valid (1415 total entries) - Exact Roslyn messages preserved per Bill's requirements Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: remove duplicated legacy displayName tail from expression-form TOC entry The displayName folded scalar contained a duplicated five-code tail (CS8115, CS8185, CS8209, CS8310, CS8312 with associated phrases) left over from the original commit after the eight-code expansion appended new content without removing the old suffix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: restructure expression-form-restrictions with single H2 section and bullet-point remediation Consolidate five separate H2/H3 subsections into one unified 'Invalid expression contexts' H2 section with scannable bullet-point remediation guidance. All eight diagnostic codes (CS0175, CS0186, CS1547, CS8115, CS8185, CS8209, CS8310, CS8312) now appear as cohesive remedy bullets, preserving all substantive examples and guidance while following prevailing style in delegate-function-pointer-diagnostics.md and foreach-diagnostics.md. Changes: - Removed old H2/H3 headers (Base keyword, Null iteration, Void keyword, Throw expressions, Declaration expressions, Target type required, Void-returning) - Created unified 'Invalid expression contexts' H2 with all anchors consolidated - Restructured master error list to link all codes to single #invalid-expression-contexts - Updated redirects for cs0175.md, cs0186.md, cs1547.md to point to unified anchor - Preserved all code examples, remediation patterns, and guidance from original Validation: 8 codes in front matter, 8 bullet items, 2 code examples, 3 redirects updated, no trailing whitespace, single H2 anchor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Proofread and finalize. * Update docs/csharp/language-reference/compiler-messages/expression-form-restrictions.md Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> --- .openpublishing.redirection.csharp.json | 15 ++++ .../expression-form-restrictions.md | 79 +++++++++++++++++++ docs/csharp/language-reference/toc.yml | 12 +-- docs/csharp/misc/cs0175.md | 46 ----------- docs/csharp/misc/cs0186.md | 31 -------- docs/csharp/misc/cs1547.md | 32 -------- ...n-t-have-specifics-on-this-csharp-error.md | 5 -- 7 files changed, 100 insertions(+), 120 deletions(-) create mode 100644 docs/csharp/language-reference/compiler-messages/expression-form-restrictions.md delete mode 100644 docs/csharp/misc/cs0175.md delete mode 100644 docs/csharp/misc/cs0186.md delete mode 100644 docs/csharp/misc/cs1547.md diff --git a/.openpublishing.redirection.csharp.json b/.openpublishing.redirection.csharp.json index 213055a1d48e7..1dbe944505b88 100644 --- a/.openpublishing.redirection.csharp.json +++ b/.openpublishing.redirection.csharp.json @@ -5840,6 +5840,21 @@ { "source_path_from_root": "/docs/csharp/programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md", "redirect_url": "/dotnet/csharp/fundamentals/expressions/equality#implement-equality-yourself-when-a-type-cant-be-a-record" + }, + { + "source_path": "docs/csharp/misc/cs0175.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/expression-form-restrictions#invalid-expression-contexts", + "redirect_document_id": "false" + }, + { + "source_path": "docs/csharp/misc/cs0186.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/expression-form-restrictions#invalid-expression-contexts", + "redirect_document_id": "false" + }, + { + "source_path": "docs/csharp/misc/cs1547.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/expression-form-restrictions#invalid-expression-contexts", + "redirect_document_id": "false" } ] } diff --git a/docs/csharp/language-reference/compiler-messages/expression-form-restrictions.md b/docs/csharp/language-reference/compiler-messages/expression-form-restrictions.md new file mode 100644 index 0000000000000..160eb65ae9214 --- /dev/null +++ b/docs/csharp/language-reference/compiler-messages/expression-form-restrictions.md @@ -0,0 +1,79 @@ +--- +title: "Resolve errors from invalid expression contexts" +description: "This article helps you diagnose and correct C# compiler errors and warnings from expressions that appear in contexts where they are not permitted" +f1_keywords: + - "CS0175" + - "CS0186" + - "CS1547" + - "CS8115" + - "CS8185" + - "CS8209" + - "CS8310" + - "CS8312" +helpviewer_keywords: + - "CS0175" + - "CS0186" + - "CS1547" + - "CS8115" + - "CS8185" + - "CS8209" + - "CS8310" + - "CS8312" +ms.date: 08/20/2026 +ai-usage: ai-assisted +--- + +# Resolve errors from invalid expression contexts + +This article covers the following compiler errors and warnings: + + + +- [**CS0175**](#invalid-expression-contexts): *Use of keyword 'base' is not valid in this context* +- [**CS0186**](#invalid-expression-contexts): *Use of null is not valid in this context* +- [**CS1547**](#invalid-expression-contexts): *Keyword 'void' cannot be used in this context* +- [**CS8115**](#invalid-expression-contexts): *A throw expression is not allowed in this context.* +- [**CS8185**](#invalid-expression-contexts): *A declaration is not allowed in this context.* +- [**CS8209**](#invalid-expression-contexts): *A value of type 'void' may not be assigned.* +- [**CS8310**](#invalid-expression-contexts): *Operator 'operator' cannot be applied to operand 'operand'* +- [**CS8312**](#invalid-expression-contexts): *Use of default literal is not valid in this context* + +## Invalid expression contexts + +The following diagnostics identify expressions that appear in contexts where they're not permitted. These errors typically arise when you use keywords, literals, or expressions in positions where the compiler doesn't permit them. The remediation strategy depends on the specific keyword or expression type. + +- **CS0175**: *Use of keyword 'base' is not valid in this context*. Use the `base` keyword to access a specific member of the base class. Don't use it as a standalone expression. When you need to reference the base class, access a member explicitly: `base.MemberName` instead of just `base`. For example, avoid `Console.WriteLine(base);` and instead use `Console.WriteLine(base.Member);`. Similarly, don't attempt to assign to `base` directly; instead, assign to a specific base member: `base.Member = value;`. Ensure you always specify which base class member you need to access. This error also occurs when using `base` outside of an instance method in a derived class. + +- **CS0186**: *Use of null is not valid in this context*. You can't use the `null` literal in contexts where the compiler expects a concrete collection or enumerable. This error commonly occurs in `foreach` loops where `null` is provided as the collection to iterate over. In a `foreach` loop, the collection must implement `IEnumerable` (or similar interface) and must be non-null. If your data source might be null, check for null before the loop: + + ```csharp + IEnumerable collection = /* your source */; + if (collection != null) + { + foreach (var item in collection) + { + // Process item + } + } + ``` + + Alternatively, use a null-coalescing operator or default empty collection: + + ```csharp + foreach (var item in collection ?? Enumerable.Empty()) + { + // Process item + } + ``` + +- **CS1547**: *Keyword 'void' cannot be used in this context*. The `void` keyword indicates the absence of a return value and can't be used as a variable type or field type. It's valid only as a method return type (`void Method() { }`), a delegate return type (`public delegate void Action();`), or a pointer-to-void in unsafe code (`void* ptr;`). If you encounter this error, you're likely attempting to declare a variable of type `void`, which is invalid. Instead, choose an appropriate type for the variable. If you need a method that performs an action without returning a value, call it as a standalone statement (`MyMethod();`); don't attempt to assign its result. + +- **CS8115**: *A throw expression is not allowed in this context.*. The compiler permits throw expressions only in specific contexts where an expression can appear and the exception is immediately propagated. Move the `throw` expression to a valid context, such as a conditional arm of a ternary or switch expression (where the throw is one of the arms), an assignment to evaluate the throw in place of the assigned value, or an argument to a method call where throwing is appropriate. A throw expression can also appear in a statement in a lambda or local function body (not within a method that must return a value). If the throw expression appears in a position where the expression value must be used (such as within arithmetic or operator expressions), extract it into a separate statement or conditional check. + +- **CS8185**: *A declaration is not allowed in this context.*. The compiler permits declaration expressions (`out var`, pattern-matching declarations) only in specific positions, including `out` parameter declarations in method calls and in certain statement contexts. Remove the declaration expression from contexts where it's not permitted, such as lambda expression bodies (unless the lambda is a statement body), query expressions where the grammar forbids declarations, inside attribute arguments, or in contexts that require a read-only expression. If you need to use an out variable, call the method in a separate statement, then reference the resulting variable. Alternatively, use a local variable declaration before the expression. + +- **CS8209**: *A value of type 'void' may not be assigned.*. The `void` type isn't a value type; it represents the absence of a return value. Remove the assignment of void-returning expressions. If you need to invoke a method that returns `void`, call it as a standalone statement. If you need a result, use a method that returns a value instead. Ensure that expressions assigned to variables always have a meaningful return type. + +- **CS8310**: *Operator 'operator' cannot be applied to operand 'operand'* and **CS8312**: *Use of default literal is not valid in this context*. The `default` literal and typeless expressions (such as `null` or `new` without a target type) require a target type for the compiler to infer the expression type. When an operator is applied to these expressions without enough context, the compiler can't determine the operand type. Provide the target type by adding an explicit type cast (`(int)default` or `(MyType)new`), assigning to a typed variable (`int x = default;`), using a method parameter or return type to establish context, or using the verbose form `default(Type)` instead of the `default` literal for clarity. If the operator itself requires a specific type, ensure the operand can be implicitly converted to that type. diff --git a/docs/csharp/language-reference/toc.yml b/docs/csharp/language-reference/toc.yml index 81dedbab5cf4c..134f4cf632856 100644 --- a/docs/csharp/language-reference/toc.yml +++ b/docs/csharp/language-reference/toc.yml @@ -732,6 +732,12 @@ items: CS1688, CS1706, CS1731, CS1732, CS1764, CS1911, CS1989, CS3006, CS8030, CS8175, CS8820, CS8821, CS8916, CS8917, CS8934, CS8971, CS8972, CS8974, CS8975, CS9098, CS9099, CS9100, CS9236 + - name: Expression-form restrictions + href: ./compiler-messages/expression-form-restrictions.md + displayName: > + keyword contexts, base keyword, null, void, throw expressions, + declaration expressions, invalid contexts, + CS0175, CS0186, CS1547, CS8115, CS8185, CS8209, CS8310, CS8312 - name: Local functions href: ./compiler-messages/local-function-errors.md displayName: > @@ -991,8 +997,6 @@ items: href: ./compiler-messages/cs0173.md - name: CS0174 href: ../misc/cs0174.md - - name: CS0175 - href: ../misc/cs0175.md - name: CS0176 href: ../misc/cs0176.md - name: CS0177 @@ -1001,8 +1005,6 @@ items: href: ../misc/cs0179.md - name: CS0180 href: ../misc/cs0180.md - - name: CS0186 - href: ../misc/cs0186.md - name: CS0191 href: ../misc/cs0191.md - name: CS0198 @@ -1363,8 +1365,6 @@ items: href: ../misc/cs1542.md - name: CS1545 href: ../misc/cs1545.md - - name: CS1547 - href: ../misc/cs1547.md - name: CS1548 href: ./compiler-messages/cs1548.md - name: CS1551 diff --git a/docs/csharp/misc/cs0175.md b/docs/csharp/misc/cs0175.md deleted file mode 100644 index 929f08a65c6b1..0000000000000 --- a/docs/csharp/misc/cs0175.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -description: "Compiler Error CS0175" -title: "Compiler Error CS0175" -ms.date: 07/20/2015 -f1_keywords: - - "CS0175" -helpviewer_keywords: - - "CS0175" -ms.assetid: cedd769d-8258-4235-a321-362981b9f84b ---- -# Compiler Error CS0175 - -Use of keyword 'base' is not valid in this context - - The [base](../language-reference/keywords/base.md) keyword must be used to specify a particular member of the base class. For more information, see [Constructors](../programming-guide/classes-and-structs/constructors.md). - - The following sample generates CS0175: - -```csharp -// CS0175.cs -using System; -class BaseClass -{ - public int TestInt = 0; -} - -class MyClass : BaseClass -{ - public static void Main() - { - MyClass aClass = new MyClass(); - aClass.BaseTest(); - } - - public void BaseTest() - { - Console.WriteLine(base); // CS0175 - // Try the following line instead: - // Console.WriteLine(base.TestInt); - base = 9; // CS0175 - - // Try the following line instead: - // base.TestInt = 9; - } -} -``` diff --git a/docs/csharp/misc/cs0186.md b/docs/csharp/misc/cs0186.md deleted file mode 100644 index de4e61db1d84a..0000000000000 --- a/docs/csharp/misc/cs0186.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -description: "Compiler Error CS0186" -title: "Compiler Error CS0186" -ms.date: 07/20/2015 -f1_keywords: - - "CS0186" -helpviewer_keywords: - - "CS0186" -ms.assetid: b8afca3e-0fb9-44c5-b4bb-abe3ef134e85 ---- -# Compiler Error CS0186 - -Use of null is not valid in this context - - The following sample generates CS0186: - -```csharp -// CS0186.cs -using System; -using System.Collections; - -class MyClass -{ - static void Main() - { - // Each of the following lines generates CS0186: - foreach (int i in null) {} // CS0186 - foreach (int i in (IEnumerable) null) { }; // CS0186 - } -} -``` diff --git a/docs/csharp/misc/cs1547.md b/docs/csharp/misc/cs1547.md deleted file mode 100644 index 298177d530c65..0000000000000 --- a/docs/csharp/misc/cs1547.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -description: "Compiler Error CS1547" -title: "Compiler Error CS1547" -ms.date: 07/20/2015 -f1_keywords: - - "CS1547" -helpviewer_keywords: - - "CS1547" -ms.assetid: 40029557-076a-47d8-aabc-d86c56a846d7 ---- -# Compiler Error CS1547 - -Keyword 'void' cannot be used in this context - - The compiler detected an invalid use of the [void](../language-reference/builtin-types/void.md) keyword. - - The following sample generates CS1547: - -```csharp -// CS1547.cs -public class MyClass -{ - void BadMethod() - { - void i; // CS1547, cannot have variables of type void - } - - public static void Main() - { - } -} -``` 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 10affda335669..0d31e10203c57 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 @@ -157,9 +157,7 @@ f1_keywords: - "CS8111" - "CS8113" # C# 7.0 diagnostics - - "CS8115" - "CS8180" - - "CS8185" - "CS8188" - "CS8189" - "CS8190" @@ -168,15 +166,12 @@ f1_keywords: - "CS8202" - "CS8205" - "CS8206" - - "CS8209" # C# 7.1 diagnostics - "CS8300" - "CS8301" - "CS8305" - "CS8308" - "CS8309" - - "CS8310" - - "CS8312" # C# 7.2 diagnostics - "CS8323" - "CS8328" From aae0847b8ace4aef41b57f4a19d0de213bfb9fcd Mon Sep 17 00:00:00 2001 From: "azure-sdk-automation[bot]" <191533747+azure-sdk-automation[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 17:01:45 -0700 Subject: [PATCH 5/9] Update package index with latest published versions (#55620) Co-authored-by: azure-sdk --- docs/azure/includes/dotnet-all.md | 6 +++--- docs/azure/includes/dotnet-new.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/azure/includes/dotnet-all.md b/docs/azure/includes/dotnet-all.md index 0185a29012f57..5d24598094ee9 100644 --- a/docs/azure/includes/dotnet-all.md +++ b/docs/azure/includes/dotnet-all.md @@ -36,7 +36,7 @@ | Conversational Language Understanding | NuGet [1.1.0](https://www.nuget.org/packages/Azure.AI.Language.Conversations/1.1.0)
NuGet [2.0.0-beta.5](https://www.nuget.org/packages/Azure.AI.Language.Conversations/2.0.0-beta.5) | [docs](/dotnet/api/overview/azure/AI.Language.Conversations-readme) | GitHub [1.1.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.AI.Language.Conversations_1.1.0/sdk/cognitivelanguage/Azure.AI.Language.Conversations/)
GitHub [2.0.0-beta.5](https://github.com/Azure/azure-sdk-for-net/tree/Azure.AI.Language.Conversations_2.0.0-beta.5/sdk/cognitivelanguage/Azure.AI.Language.Conversations/) | | Conversations Authoring | NuGet [1.0.0-beta.3](https://www.nuget.org/packages/Azure.AI.Language.Conversations.Authoring/1.0.0-beta.3) | [docs](/dotnet/api/overview/azure/AI.Language.Conversations.Authoring-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.3](https://github.com/Azure/azure-sdk-for-net/tree/Azure.AI.Language.Conversations.Authoring_1.0.0-beta.3/sdk/cognitivelanguage/Azure.AI.Language.Conversations.Authoring/) | | Core - Client - AMQP | NuGet [1.3.1](https://www.nuget.org/packages/Azure.Core.Amqp/1.3.1) | [docs](/dotnet/api/overview/azure/Core.Amqp-readme) | GitHub [1.3.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Core.Amqp_1.3.1/sdk/core/Azure.Core.Amqp/) | -| Core - Client - Core | NuGet [1.61.0](https://www.nuget.org/packages/Azure.Core/1.61.0) | [docs](/dotnet/api/overview/azure/Core-readme) | GitHub [1.61.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Core_1.61.0/sdk/core/Azure.Core/) | +| Core - Client - Core | NuGet [1.62.0](https://www.nuget.org/packages/Azure.Core/1.62.0) | [docs](/dotnet/api/overview/azure/Core-readme) | GitHub [1.62.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Core_1.62.0/sdk/core/Azure.Core/) | | Core - Client - Core | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Core.Expressions.DataFactory/1.0.0) | [docs](/dotnet/api/overview/azure/Core.Expressions.DataFactory-readme) | GitHub [1.0.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Core.Expressions.DataFactory_1.0.0/sdk/core/Azure.Core.Expressions.DataFactory/) | | Core Newtonsoft Json | NuGet [2.0.0](https://www.nuget.org/packages/Microsoft.Azure.Core.NewtonsoftJson/2.0.0) | [docs](/dotnet/api/overview/azure/Microsoft.Azure.Core.NewtonsoftJson-readme) | GitHub [2.0.0](https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.Azure.Core.NewtonsoftJson_2.0.0/sdk/core/Microsoft.Azure.Core.NewtonsoftJson/) | | Core WCF Storage Queues | NuGet [1.0.0-beta.1](https://www.nuget.org/packages/Microsoft.CoreWCF.Azure.StorageQueues/1.0.0-beta.1) | [docs](/dotnet/api/overview/azure/Microsoft.CoreWCF.Azure.StorageQueues-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.1](https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.CoreWCF.Azure.StorageQueues_1.0.0-beta.1/sdk/extension-wcf/Microsoft.CoreWCF.Azure.StorageQueues/) | @@ -626,8 +626,8 @@ | Functions extension for Azure SQL and SQL Server | NuGet [3.1.536](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.Sql/3.1.536) | | | | Functions extension for Cosmos DB | NuGet [4.16.1](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.CosmosDB/4.16.1) | | GitHub [4.16.1](https://github.com/Azure/azure-webjobs-sdk-extensions/tree/cosmos-v3.0.7/src/WebJobs.Extensions.CosmosDB) | | Functions extension for DocumentDB | NuGet [1.3.0](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.DocumentDB/1.3.0) | | GitHub [1.3.0](https://github.com/Azure/azure-webjobs-sdk-extensions) | -| Functions extension for Durable Task Framework | NuGet [3.14.1](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.DurableTask/3.14.1) | [docs](/dotnet/api/overview/azure/functions) | GitHub [3.14.1](https://github.com/Azure/azure-functions-durable-extension/tree/v2.2.2/src/WebJobs.Extensions.DurableTask) | -| Functions extension for Durable Task Framework - isolated worker | NuGet [1.18.0](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.DurableTask/1.18.0) | | | +| Functions extension for Durable Task Framework | NuGet [3.15.0](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.DurableTask/3.15.0) | [docs](/dotnet/api/overview/azure/functions) | GitHub [3.15.0](https://github.com/Azure/azure-functions-durable-extension/tree/v2.2.2/src/WebJobs.Extensions.DurableTask) | +| Functions extension for Durable Task Framework - isolated worker | NuGet [1.19.0](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.DurableTask/1.19.0) | | | | Functions extension for HTTP | NuGet [3.3.0](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.Http/3.3.0) | | GitHub [3.3.0](https://github.com/Azure/azure-webjobs-sdk-extensions/tree/v3.0.2/src/WebJobs.Extensions.Http) | | Functions extension for IoT Edge | NuGet [1.0.7](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.EdgeHub/1.0.7) | | GitHub [1.0.7](https://github.com/Azure/iotedge/tree/1.0.7/edge-hub) | | Functions extension for Kafka | NuGet [4.3.2](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.Kafka/4.3.2) | | GitHub [4.3.2](https://github.com/Azure/azure-functions-kafka-extension/tree/3.0.0/src/Microsoft.Azure.WebJobs.Extensions.Kafka) | diff --git a/docs/azure/includes/dotnet-new.md b/docs/azure/includes/dotnet-new.md index 28612ad0b9ef8..4090a1d7aeb23 100644 --- a/docs/azure/includes/dotnet-new.md +++ b/docs/azure/includes/dotnet-new.md @@ -40,7 +40,7 @@ | Conversational Language Understanding | NuGet [1.1.0](https://www.nuget.org/packages/Azure.AI.Language.Conversations/1.1.0)
NuGet [2.0.0-beta.5](https://www.nuget.org/packages/Azure.AI.Language.Conversations/2.0.0-beta.5) | [docs](/dotnet/api/overview/azure/AI.Language.Conversations-readme) | GitHub [1.1.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.AI.Language.Conversations_1.1.0/sdk/cognitivelanguage/Azure.AI.Language.Conversations/)
GitHub [2.0.0-beta.5](https://github.com/Azure/azure-sdk-for-net/tree/Azure.AI.Language.Conversations_2.0.0-beta.5/sdk/cognitivelanguage/Azure.AI.Language.Conversations/) | | Conversations Authoring | NuGet [1.0.0-beta.3](https://www.nuget.org/packages/Azure.AI.Language.Conversations.Authoring/1.0.0-beta.3) | [docs](/dotnet/api/overview/azure/AI.Language.Conversations.Authoring-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.3](https://github.com/Azure/azure-sdk-for-net/tree/Azure.AI.Language.Conversations.Authoring_1.0.0-beta.3/sdk/cognitivelanguage/Azure.AI.Language.Conversations.Authoring/) | | Core - Client - AMQP | NuGet [1.3.1](https://www.nuget.org/packages/Azure.Core.Amqp/1.3.1) | [docs](/dotnet/api/overview/azure/Core.Amqp-readme) | GitHub [1.3.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Core.Amqp_1.3.1/sdk/core/Azure.Core.Amqp/) | -| Core - Client - Core | NuGet [1.61.0](https://www.nuget.org/packages/Azure.Core/1.61.0) | [docs](/dotnet/api/overview/azure/Core-readme) | GitHub [1.61.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Core_1.61.0/sdk/core/Azure.Core/) | +| Core - Client - Core | NuGet [1.62.0](https://www.nuget.org/packages/Azure.Core/1.62.0) | [docs](/dotnet/api/overview/azure/Core-readme) | GitHub [1.62.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Core_1.62.0/sdk/core/Azure.Core/) | | Core - Client - Core | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Core.Expressions.DataFactory/1.0.0) | [docs](/dotnet/api/overview/azure/Core.Expressions.DataFactory-readme) | GitHub [1.0.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.Core.Expressions.DataFactory_1.0.0/sdk/core/Azure.Core.Expressions.DataFactory/) | | Core Newtonsoft Json | NuGet [2.0.0](https://www.nuget.org/packages/Microsoft.Azure.Core.NewtonsoftJson/2.0.0) | [docs](/dotnet/api/overview/azure/Microsoft.Azure.Core.NewtonsoftJson-readme) | GitHub [2.0.0](https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.Azure.Core.NewtonsoftJson_2.0.0/sdk/core/Microsoft.Azure.Core.NewtonsoftJson/) | | Core WCF Storage Queues | NuGet [1.0.0-beta.1](https://www.nuget.org/packages/Microsoft.CoreWCF.Azure.StorageQueues/1.0.0-beta.1) | [docs](/dotnet/api/overview/azure/Microsoft.CoreWCF.Azure.StorageQueues-readme?view=azure-dotnet-preview&preserve-view=true) | GitHub [1.0.0-beta.1](https://github.com/Azure/azure-sdk-for-net/tree/Microsoft.CoreWCF.Azure.StorageQueues_1.0.0-beta.1/sdk/extension-wcf/Microsoft.CoreWCF.Azure.StorageQueues/) | From 5f27514f842767f15c8933a312acabf85fc97289 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 21 Aug 2026 09:42:51 -0400 Subject: [PATCH 6/9] Slim conditional operator coverage in selection statements (#55613) * Slim conditional operator coverage in selection statements (#55335) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * rebase and update links. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/csharp/fundamentals/statements/selection.md | 8 +++----- .../snippets/selection-statements/Program.cs | 13 ------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/docs/csharp/fundamentals/statements/selection.md b/docs/csharp/fundamentals/statements/selection.md index 5b7a3eea967cb..58ebf2ed649f9 100644 --- a/docs/csharp/fundamentals/statements/selection.md +++ b/docs/csharp/fundamentals/statements/selection.md @@ -1,7 +1,7 @@ --- title: "Selection statements in C#" description: Use if, else, and switch statements to choose which code runs based on a condition, including pattern-based case labels and when clauses. -ms.date: 07/09/2026 +ms.date: 08/20/2026 ms.topic: concept-article ai-usage: ai-assisted --- @@ -55,11 +55,9 @@ The `if` and `switch` statements decide which code runs. When you instead need t ### Conditional operator `?:` -The conditional operator `?:` chooses between two values based on a Boolean condition. It takes the form `condition ? valueIfTrue : valueIfFalse`: +The conditional operator `?:` chooses one of two values based on a Boolean condition. -:::code language="csharp" source="./snippets/selection-statements/Program.cs" id="Ternary"::: - -Use `?:` when you assign one of two values, because it keeps the assignment in one place and lets you mark the variable `readonly` or `const`. Prefer an `if` statement when the branches do more than produce a value. For the operator's precedence and associativity rules, see the [conditional operator](../../language-reference/operators/conditional-operator.md) in the language reference. +For its syntax, short-circuit behavior, and guidance on choosing it instead of `if`/`else`, see [Conditional operator `?:`](../expressions/operators.md#conditional-operator-). ### `switch` expression diff --git a/docs/csharp/fundamentals/statements/snippets/selection-statements/Program.cs b/docs/csharp/fundamentals/statements/snippets/selection-statements/Program.cs index f0c087ac20662..b9640e90f47e7 100644 --- a/docs/csharp/fundamentals/statements/snippets/selection-statements/Program.cs +++ b/docs/csharp/fundamentals/statements/snippets/selection-statements/Program.cs @@ -8,7 +8,6 @@ public static void Main() ElseIfExample(); SwitchStatementExample(); SwitchWhenExample(); - TernaryExample(); } private static void IfElseExample() @@ -104,16 +103,4 @@ private static void SwitchWhenExample() // } - private static void TernaryExample() - { - // - int hour = 9; - - // The conditional operator ?: chooses between two values in a single - // expression: condition ? valueIfTrue : valueIfFalse. - string greeting = hour < 12 ? "Good morning" : "Good afternoon"; - - Console.WriteLine(greeting); // => Good morning - // - } } From 1fd0a4dfd14f5a09bfd071b433409c5998c5ee47 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Aug 2026 09:49:23 -0700 Subject: [PATCH 7/9] Document .NET 11 `Assembly.GetCallingAssembly` behavior with `StackTraceSupport=false` (#55213) --- docs/core/compatibility/11.md | 1 + ...lingassembly-stacktracesupport-disabled.md | 46 +++++++++++++++++++ docs/core/compatibility/toc.yml | 2 + .../deploying/trimming/trimming-options.md | 2 +- 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 docs/core/compatibility/core-libraries/11/assembly-getcallingassembly-stacktracesupport-disabled.md diff --git a/docs/core/compatibility/11.md b/docs/core/compatibility/11.md index ddfa1444b0a4c..b9a18809ad550 100644 --- a/docs/core/compatibility/11.md +++ b/docs/core/compatibility/11.md @@ -22,6 +22,7 @@ See [Breaking changes in ASP.NET Core 11](/aspnet/core/breaking-changes/11/overv | Title | Type of change | |-------------------------------------------------------------------|-------------------| +| [Assembly.GetCallingAssembly behavior changes when stack trace support is disabled](core-libraries/11/assembly-getcallingassembly-stacktracesupport-disabled.md) | Behavioral change | | [CborReader and CborWriter enforce a default maximum nesting depth](core-libraries/11/cbor-max-depth.md) | Behavioral change | | [Complex special-value results now follow C23 Annex G](core-libraries/11/complex-annex-g-special-values.md) | Behavioral change | | [CRC32 validation added when reading ZIP archive entries](core-libraries/11/ziparchive-entry-crc32-validation.md) | Behavioral change | diff --git a/docs/core/compatibility/core-libraries/11/assembly-getcallingassembly-stacktracesupport-disabled.md b/docs/core/compatibility/core-libraries/11/assembly-getcallingassembly-stacktracesupport-disabled.md new file mode 100644 index 0000000000000..a6a0615b55d7e --- /dev/null +++ b/docs/core/compatibility/core-libraries/11/assembly-getcallingassembly-stacktracesupport-disabled.md @@ -0,0 +1,46 @@ +--- +title: "Breaking change: Assembly.GetCallingAssembly behavior changes when stack trace support is disabled" +description: "Learn about the breaking change in .NET 11 where Assembly.GetCallingAssembly can throw NotSupportedException when stack trace support is disabled." +ms.date: 08/03/2026 +ai-usage: ai-assisted +--- + +# Assembly.GetCallingAssembly behavior changes when stack trace support is disabled + + now supports Native AOT and uses stack trace data to resolve the caller. If stack trace support is disabled, the method now throws on both Native AOT and CoreCLR. + +## Version introduced + +.NET 11 Preview 7 + +## Previous behavior + +Previously, on Native AOT, always threw . Previously, on CoreCLR, the method returned the calling assembly even if `StackTraceSupport` was set to `false`. + +## New behavior + +Starting in .NET 11, on Native AOT, returns the calling assembly by inspecting stack trace data. Starting in .NET 11, on both Native AOT and CoreCLR, the method throws if the `StackTraceSupport` feature switch is set to `false`. + +The exception message is: + +> Unable to retrieve stack trace information when StackTraceSupport feature switch is set to false. + +## Type of breaking change + +This change is a [behavioral change](../../categories.md#behavioral-change). + +## Reason for change + +To return a correct caller, requires stack trace data. If stack trace support is unavailable, the runtime can't determine the caller reliably. The runtime now throws instead of returning an incorrect result. For Native AOT support details, see [dotnet/runtime#129963](https://github.com/dotnet/runtime/pull/129963). + +## Recommended action + +If you publish with `StackTraceSupport` set to `false` and your app calls , expect . Use one of these options: + +- Enable stack trace support by removing the switch or setting `StackTraceSupport` to `true`. +- Remove calls to . +- Catch and handle the fallback path explicitly. + +## Affected APIs + +- diff --git a/docs/core/compatibility/toc.yml b/docs/core/compatibility/toc.yml index 16db72fe6b404..0771ca5ae9dbb 100644 --- a/docs/core/compatibility/toc.yml +++ b/docs/core/compatibility/toc.yml @@ -10,6 +10,8 @@ items: href: 11.md - name: Core .NET libraries items: + - name: Assembly.GetCallingAssembly behavior change when stack-trace support disabled + href: core-libraries/11/assembly-getcallingassembly-stacktracesupport-disabled.md - name: CborReader and CborWriter enforce a default maximum nesting depth href: core-libraries/11/cbor-max-depth.md - name: Complex special-value results now follow C23 Annex G diff --git a/docs/core/deploying/trimming/trimming-options.md b/docs/core/deploying/trimming/trimming-options.md index 8f777cb024bf2..5fbc929ba00be 100644 --- a/docs/core/deploying/trimming/trimming-options.md +++ b/docs/core/deploying/trimming/trimming-options.md @@ -77,7 +77,7 @@ Several feature areas of the framework libraries come with trimmer directives th | `MetadataUpdaterSupport` | When set to `false`, removes metadata update–specific logic related to hot reload. | | `MetricsSupport` | When set to `false`, removes support for instrumentation. | | `StackTraceLineNumberSupport` (.NET 11+) | (`PublishAot` only.) When set to `true`, generates additional line number information in the output executable module. Stack traces (for example, and ) will include information about file names and line numbers at runtime. This information is similar to the information generated into debugging symbol files (PDB/DWO/dSYM files). However, for apps published with `PublishAot`, the runtime doesn't read the native symbol files and the debugging symbols are only used by debuggers. | -| `StackTraceSupport` (.NET 8+) | When set to `false`, removes support for generating stack traces (for example, or ) by the runtime. The amount of information that is removed from stack trace strings might depend on other deployment options. This option does not affect stack traces generated by debuggers. | +| `StackTraceSupport` (.NET 8+) | When set to `false`, removes support for generating stack traces (for example, or ) by the runtime. Methods that require stack trace data, such as , throw in .NET 11 and later when this option is `false`. The amount of information that is removed from stack trace strings might depend on other deployment options. This option does not affect stack traces generated by debuggers. | | `UseNativeHttpHandler` | When set to `true`, uses the default platform implementation of for Android and iOS and removes the managed implementation. | | `UseSizeOptimizedLinq` (.NET 10+) | When set to `true`, removes some of the throughput optimizations in LINQ that adversely affect the size of the application. Defaults to `true` with `PublishAot`; it might not be possible to natively compile some applications with this property set to `false`. | | `UseSystemResourceKeys` | When set to `true`, strips exception messages for `System.*` assemblies. When an exception is thrown from a `System.*` assembly, the message is a simplified resource ID instead of the full message. | From df5d9974685be3d3948828182def5a78a8dc4d7d Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 21 Aug 2026 16:34:32 -0400 Subject: [PATCH 8/9] Add statements overview article (#55616) * Consolidate C# statements documentation (#55336) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Revise C# statements learning flow (#55336) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * proofread and finalize --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .openpublishing.redirection.csharp.json | 10 +- docs/csharp/fundamentals/expressions/index.md | 4 +- .../null-safety/null-operators.md | 4 + .../fundamentals/program-structure/index.md | 2 +- docs/csharp/fundamentals/statements/index.md | 95 +++ .../snippets/statements-overview/Program.cs | 41 ++ .../statements-overview.csproj | 8 +- .../tutorials/nullable-reference-types.md | 2 +- .../fundamentals/types/built-in-types.md | 4 + .../compiler-messages/cs0201.md | 2 +- .../keywords/statement-keywords.md | 4 +- .../language-reference/operators/default.md | 2 +- .../language-reference/operators/index.md | 4 +- .../operators/lambda-operator.md | 21 +- .../operators/null-coalescing-operator.md | 2 +- .../statements/selection-statements.md | 2 +- docs/csharp/misc/cs1002.md | 7 +- .../classes-and-structs/constructors.md | 2 +- .../expression-bodied-members.md | 100 --- .../snippets/equality-comparisons/Program.cs | 22 + .../equality-comparisons.csproj | 9 +- .../statements.md | 89 --- docs/csharp/toc.yml | 16 +- .../whats-new/csharp-version-history.md | 4 +- .../code-analysis/style-rules/ide0021.md | 4 +- .../code-analysis/style-rules/ide0022.md | 4 +- .../style-rules/ide0023-ide0024.md | 4 +- .../code-analysis/style-rules/ide0025.md | 4 +- .../code-analysis/style-rules/ide0026.md | 4 +- .../code-analysis/style-rules/ide0027.md | 4 +- .../code-analysis/style-rules/ide0053.md | 4 +- .../code-analysis/style-rules/ide0061.md | 4 +- .../csProgGuideStatements/CS/Program.cs | 14 - .../csProgGuideStatements/CS/Statements.cs | 638 ------------------ .../ExpressionBodiedMembers/Program.cs | 2 - .../expr-bodied-ctor.cs | 46 -- .../expr-bodied-event.cs | 42 -- .../expr-bodied-indexers.cs | 30 - .../expr-bodied-methods.cs | 40 -- .../expr-bodied-readonly.cs | 26 - 40 files changed, 237 insertions(+), 1089 deletions(-) create mode 100644 docs/csharp/fundamentals/statements/index.md create mode 100644 docs/csharp/fundamentals/statements/snippets/statements-overview/Program.cs rename samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/ExpressionBodiedMembers.csproj => docs/csharp/fundamentals/statements/snippets/statements-overview/statements-overview.csproj (64%) delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md create mode 100644 docs/csharp/programming-guide/statements-expressions-operators/snippets/equality-comparisons/Program.cs rename samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.csproj => docs/csharp/programming-guide/statements-expressions-operators/snippets/equality-comparisons/equality-comparisons.csproj (55%) delete mode 100644 docs/csharp/programming-guide/statements-expressions-operators/statements.md delete mode 100644 samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Program.cs delete mode 100644 samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs delete mode 100644 samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/Program.cs delete mode 100644 samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-ctor.cs delete mode 100644 samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-event.cs delete mode 100644 samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-indexers.cs delete mode 100644 samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-methods.cs delete mode 100644 samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-readonly.cs diff --git a/.openpublishing.redirection.csharp.json b/.openpublishing.redirection.csharp.json index 1dbe944505b88..12d21627db593 100644 --- a/.openpublishing.redirection.csharp.json +++ b/.openpublishing.redirection.csharp.json @@ -5068,6 +5068,10 @@ "source_path_from_root": "/docs/csharp/programming-guide/statements-expressions-operators/default-value-expressions.md", "redirect_url": "/dotnet/csharp/language-reference/operators/default" }, + { + "source_path_from_root": "/docs/csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md", + "redirect_url": "/dotnet/csharp/language-reference/operators/lambda-operator#expression-body-definition" + }, { "source_path_from_root": "/docs/csharp/programming-guide/statements-expressions-operators/expressions.md", "redirect_url": "/dotnet/csharp/language-reference/operators/index" @@ -5090,7 +5094,7 @@ }, { "source_path_from_root": "/docs/csharp/programming-guide/statements-expressions-operators/index.md", - "redirect_url": "/dotnet/csharp/programming-guide/statements-expressions-operators/statements" + "redirect_url": "/dotnet/csharp/fundamentals/statements" }, { "source_path_from_root": "/docs/csharp/programming-guide/statements-expressions-operators/lambda-expressions.md", @@ -5104,6 +5108,10 @@ "source_path_from_root": "/docs/csharp/programming-guide/statements-expressions-operators/overloadable-operators.md", "redirect_url": "/dotnet/csharp/language-reference/operators/operator-overloading#overloadable-operators" }, + { + "source_path_from_root": "/docs/csharp/programming-guide/statements-expressions-operators/statements.md", + "redirect_url": "/dotnet/csharp/fundamentals/statements" + }, { "source_path_from_root": "/docs/csharp/programming-guide/statements-expressions-operators/using-conversion-operators.md", "redirect_url": "/dotnet/csharp/language-reference/operators/user-defined-conversion-operators" diff --git a/docs/csharp/fundamentals/expressions/index.md b/docs/csharp/fundamentals/expressions/index.md index a8eb292482c94..542c2d47b6185 100644 --- a/docs/csharp/fundamentals/expressions/index.md +++ b/docs/csharp/fundamentals/expressions/index.md @@ -19,7 +19,9 @@ The simplest expressions are *literals* (like `42` or `"hello"`) and *variable n ### Expressions and statements -An expression produces a value. A *statement* is a complete instruction that the program executes. Many statements contain expressions. For example, `int total = 3 + 4 * 2;` is a variable declaration statement. The compiler evaluates the initializer expression `3 + 4 * 2`, which produces `11`, and assigns that value to the new variable `total`. This article focuses on expressions — how they're formed, how they're evaluated, and how they combine. +An expression produces a value. A *statement* is a complete instruction that the program executes. Many statements contain expressions. For example, `int total = 3 + 4 * 2;` is a variable declaration statement. The compiler evaluates the initializer expression `3 + 4 * 2`, which produces `11`, and assigns that value to the new variable `total`. + +You can think of an expression like a phrase and a [C# statement](../statements/index.md) like a complete sentence. These working definitions help you understand the formal terminology and explanations you'll encounter as you learn more about C#, though ordinary coding rarely requires you to remember the distinction consciously. ## Combining expressions diff --git a/docs/csharp/fundamentals/null-safety/null-operators.md b/docs/csharp/fundamentals/null-safety/null-operators.md index c485629fdc545..480527f453ac0 100644 --- a/docs/csharp/fundamentals/null-safety/null-operators.md +++ b/docs/csharp/fundamentals/null-safety/null-operators.md @@ -99,6 +99,10 @@ Use `!` sparingly, and only when you have information the compiler doesn't. Exam ## See also + + - [Null safety overview](index.md) - [Nullable value types](nullable-value-types.md) - [Nullable reference types](nullable-reference-types.md) diff --git a/docs/csharp/fundamentals/program-structure/index.md b/docs/csharp/fundamentals/program-structure/index.md index 9ea76be5d2f34..865595351061f 100644 --- a/docs/csharp/fundamentals/program-structure/index.md +++ b/docs/csharp/fundamentals/program-structure/index.md @@ -110,7 +110,7 @@ Statements often contain expressions, and expressions can nest inside other expr var maxResult = Math.Max(a, b) + Math.Max(c, d); ``` -For detailed information about statements, see [Statements](../../programming-guide/statements-expressions-operators/statements.md). For information about expression-bodied members, see [Expression-bodied members](../../programming-guide/statements-expressions-operators/expression-bodied-members.md). +For detailed information about statements, see [Statements](../statements/index.md). For information about expression-bodied members, see [Expression body definitions](../../language-reference/operators/lambda-operator.md#expression-body-definition). ## Related content diff --git a/docs/csharp/fundamentals/statements/index.md b/docs/csharp/fundamentals/statements/index.md new file mode 100644 index 0000000000000..15ec3641d4830 --- /dev/null +++ b/docs/csharp/fundamentals/statements/index.md @@ -0,0 +1,95 @@ +--- +title: "C# statements" +description: Learn how C# statements declare variables, perform actions, group code into blocks, and control the flow of execution. +ms.date: 08/20/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# C# statements + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For complete statement syntax, see [Statements](~/_csharpstandard/standard/statements.md) in the C# language specification. +> +> **Coming from another language?** Declarations, conditions, loops, and returns might be familiar. C# uses its own syntax and classification for these features, which this article introduces. + +A *statement* is a complete command: "do this." Together, the statements in a program form a recipe that the program follows from start to finish. Most statements run in sequence. Branches choose which steps to run, and loops repeat steps. + +This example declares a quantity, displays it, and then uses an `if` statement to decide whether to restock: + +:::code language="csharp" source="./snippets/statements-overview/Program.cs" id="StatementRecipe"::: + +Read the example as complete commands before looking at their parts. The declaration, the first call to `Console.WriteLine`, the entire `if` construct, and the final call to `Console.WriteLine` are statements. The block inside the `if` statement contains two more statements. + +## Statements often contain expressions + +Statements often contain *expressions*, which are pieces of code that produce values. In the preceding example, the whole `if` construct is a statement. Its condition, `quantity < 10`, is an expression that produces either `true` or `false`. + +A *declaration statement* introduces a local variable or constant. An initializer expression can provide its first value: + +```csharp +int quantity = 5; +``` + +The complete line is a declaration statement. The initializer `5` is an expression within that statement. + +An *assignment expression* stores a value in a variable, property, indexer, or other storage location. C# permits an assignment expression to form an *expression statement*: + +```csharp +quantity = 10; +``` + +This statement performs an action rather than merely calculating a value. Method calls and increment operations are other common expression statements: + +```csharp +Console.WriteLine("Restocking"); +quantity++; +``` + +Only the following expression forms can be expression statements: + +- Assignment expressions +- Method invocation expressions +- Object creation expressions +- Prefix or postfix increment and decrement expressions +- `await` expressions + +Not every expression can stand alone as a statement. For example, `quantity + 1;` computes a value but doesn't use it. It's not a valid statement. + +Here's a useful working definition: a statement is roughly like a complete sentence in English, while an [expression](../expressions/index.md) is like a phrase. This distinction helps you understand the formal terminology and explanations you'll encounter in documentation and specifications, though ordinary coding rarely requires you to remember the distinction consciously. + +## Group statements in blocks + +A *block* groups zero or more statements between braces (`{` and `}`). C# treats the group as one statement. In the opening example, the `if` statement can run both the assignment and the call to `Console.WriteLine` because a block groups them into one body. Blocks can nest inside other blocks. + +Selection and iteration statements call their body an *embedded statement*. That body can be one statement without braces or a block that groups multiple statements. + +Prefer a block even when a body contains only one statement. Braces show which statements belong to the body and prevent later edits from accidentally placing a statement outside it. + +### Blocks define variable scope + +Variables declared in a block are in scope from their declaration through the end of that block. A nested block can use variables declared by an enclosing block, but the enclosing block can't use variables declared only in the nested block: + +:::code language="csharp" source="./snippets/statements-overview/Program.cs" id="BlocksAndScope"::: + +## Choose a statement for the task + +After you recognize statements as commands, you can choose among their different kinds by purpose: + +- **Declare data:** [Declaration statements](../../language-reference/statements/declarations.md) introduce local variables and constants. +- **Perform actions:** Expression statements assign values, call methods, create objects, increment or decrement values, or await asynchronous operations. +- **Choose steps:** [Selection statements](selection.md), such as `if` and `switch`, choose which code runs. +- **Repeat steps:** [Iteration statements](iteration.md), such as `foreach`, `while`, and `for`, repeat a statement or block. +- **Transfer control:** [Jump statements](../../language-reference/statements/jump-statements.md), such as `break`, `continue`, `return`, and `yield`, move execution to another point. +- **Handle exceptions:** [Exception-handling statements](../../language-reference/statements/exception-handling-statements.md), such as `try`, `catch`, and `throw`, respond to or report errors. +- **Manage resources:** The [`using` statement](../../language-reference/statements/using.md) ensures that resources are disposed. +- **Use specialized behavior:** The [`checked` and `unchecked`](../../language-reference/statements/checked-and-unchecked.md), [`fixed`](../../language-reference/statements/fixed.md), and [`lock`](../../language-reference/statements/lock.md) statements support specific scenarios. + +## C# language specification + +For more information, see the [Statements](~/_csharpstandard/standard/statements.md) section of the [C# language specification](~/_csharpstandard/standard/README.md). + +## See also + +- [Statement keywords](../../language-reference/keywords/statement-keywords.md) +- [C# operators and expressions](../../language-reference/operators/index.md) diff --git a/docs/csharp/fundamentals/statements/snippets/statements-overview/Program.cs b/docs/csharp/fundamentals/statements/snippets/statements-overview/Program.cs new file mode 100644 index 0000000000000..3786c7a370b94 --- /dev/null +++ b/docs/csharp/fundamentals/statements/snippets/statements-overview/Program.cs @@ -0,0 +1,41 @@ +namespace StatementsOverview; + +public static class Program +{ + public static void Main() + { + ShowStatementRecipe(); + ShowBlocksAndScope(); + } + + private static void ShowStatementRecipe() + { + // + int quantity = 5; + Console.WriteLine($"Quantity: {quantity}"); // => Quantity: 5 + + if (quantity < 10) + { + quantity = 10; + Console.WriteLine("Restocked"); // => Restocked + } + + Console.WriteLine($"Quantity: {quantity}"); // => Quantity: 10 + // + } + + private static void ShowBlocksAndScope() + { + // + int outerValue = 10; + + if (outerValue > 0) + { + int innerValue = outerValue * 2; + Console.WriteLine(innerValue); // => 20 + } + + // innerValue isn't in scope here. + // + } +} diff --git a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/ExpressionBodiedMembers.csproj b/docs/csharp/fundamentals/statements/snippets/statements-overview/statements-overview.csproj similarity index 64% rename from samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/ExpressionBodiedMembers.csproj rename to docs/csharp/fundamentals/statements/snippets/statements-overview/statements-overview.csproj index 2150e3797ba5e..bad583f080c8c 100644 --- a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/ExpressionBodiedMembers.csproj +++ b/docs/csharp/fundamentals/statements/snippets/statements-overview/statements-overview.csproj @@ -1,10 +1,8 @@ - - + Exe - net8.0 - enable + net10.0 enable + enable - diff --git a/docs/csharp/fundamentals/tutorials/nullable-reference-types.md b/docs/csharp/fundamentals/tutorials/nullable-reference-types.md index 0f0e7cc57a949..fdc531badb07a 100644 --- a/docs/csharp/fundamentals/tutorials/nullable-reference-types.md +++ b/docs/csharp/fundamentals/tutorials/nullable-reference-types.md @@ -145,7 +145,7 @@ Call `PerformSurvey` from `Main`: ## Examine the survey results -To report results, expose a few helpers from `SurveyResponse` and `SurveyRun`. On `SurveyResponse`, add [expression-bodied members](../../programming-guide/statements-expressions-operators/expression-bodied-members.md) (members defined with `=>` and a single expression instead of a `{ ... }` block) that handle the nullable dictionary: +To report results, expose a few helpers from `SurveyResponse` and `SurveyRun`. On `SurveyResponse`, add [expression-bodied members](../../language-reference/operators/lambda-operator.md#expression-body-definition) (members defined with `=>` and a single expression instead of a `{ ... }` block) that handle the nullable dictionary: :::code language="csharp" source="snippets/NullableIntroduction/SurveyResponse.cs" id="SnippetSurveyStatus"::: diff --git a/docs/csharp/fundamentals/types/built-in-types.md b/docs/csharp/fundamentals/types/built-in-types.md index 8a717b9809e29..e9c88979de5dc 100644 --- a/docs/csharp/fundamentals/types/built-in-types.md +++ b/docs/csharp/fundamentals/types/built-in-types.md @@ -124,6 +124,10 @@ Use `dynamic` when interacting with COM APIs, dynamic languages, or reflection-h ## See also + + - [Type system overview](index.md) - [Built-in types (C# reference)](../../language-reference/builtin-types/built-in-types.md) - [Integral numeric types](../../language-reference/builtin-types/integral-numeric-types.md) diff --git a/docs/csharp/language-reference/compiler-messages/cs0201.md b/docs/csharp/language-reference/compiler-messages/cs0201.md index 96988e63894da..3e72ba0eadbe0 100644 --- a/docs/csharp/language-reference/compiler-messages/cs0201.md +++ b/docs/csharp/language-reference/compiler-messages/cs0201.md @@ -12,7 +12,7 @@ ms.assetid: cf5d6701-50cc-4e4f-878b-e1a4ad8a2061 Only assignment, call, increment, decrement, and new object expressions can be used as a statement - The compiler generates an error when it encounters an invalid statement. An invalid statement is any line or series of lines ending in a semicolon that does not represent an assignment ([=](../operators/assignment-operator.md)), method call [()](../operators/member-access-operators.md#invocation-expression-), [new](../operators/new-operator.md), [--](../operators/arithmetic-operators.md#decrement-operator---) or [++](../operators/arithmetic-operators.md#increment-operator-) operation. For more information, see [Statements](../../programming-guide/statements-expressions-operators/statements.md) and [Operators and expressions](../operators/index.md). + The compiler generates an error when it encounters an invalid statement. An invalid statement is any line or series of lines ending in a semicolon that does not represent an assignment ([=](../operators/assignment-operator.md)), method call [()](../operators/member-access-operators.md#invocation-expression-), [new](../operators/new-operator.md), [--](../operators/arithmetic-operators.md#decrement-operator---) or [++](../operators/arithmetic-operators.md#increment-operator-) operation. For more information, see [Statements](../../fundamentals/statements/index.md) and [Operators and expressions](../operators/index.md). ## Example 1 diff --git a/docs/csharp/language-reference/keywords/statement-keywords.md b/docs/csharp/language-reference/keywords/statement-keywords.md index 71efd51f09145..b7749ee95b84c 100644 --- a/docs/csharp/language-reference/keywords/statement-keywords.md +++ b/docs/csharp/language-reference/keywords/statement-keywords.md @@ -8,7 +8,7 @@ helpviewer_keywords: --- # Statement keywords (C# Reference) -Statements are program instructions. Except as described in the topics referenced in the following list, the program executes statements in sequence. The following list shows the C# statement keywords. For more information about statements that don't use a keyword, see [Statements](../../programming-guide/statements-expressions-operators/statements.md). +Statements are program instructions. Except as described in the topics referenced in the following list, the program executes statements in sequence. The following list shows the C# statement keywords. For more information about statements that don't use a keyword, see [Statements](../../fundamentals/statements/index.md). - [Selection statements](../statements/selection-statements.md) - `if` @@ -40,5 +40,5 @@ Statements are program instructions. Except as described in the topics reference ## See also -- [Statements](../../programming-guide/statements-expressions-operators/statements.md) +- [Statements](../../fundamentals/statements/index.md) - [C# Keywords](index.md) diff --git a/docs/csharp/language-reference/operators/default.md b/docs/csharp/language-reference/operators/default.md index 1dbfc5350001d..6c2052ab30af7 100644 --- a/docs/csharp/language-reference/operators/default.md +++ b/docs/csharp/language-reference/operators/default.md @@ -28,7 +28,7 @@ You can use the `default` literal to produce the default value of a type when th - In the assignment or initialization of a variable. - In the declaration of the default value for an [optional method parameter](../../methods.md#optional-parameters-and-arguments). - In a method call to provide an argument value. -- In a [`return` statement](../statements/jump-statements.md#the-return-statement) or as an expression in an [expression-bodied member](../../programming-guide/statements-expressions-operators/expression-bodied-members.md). +- In a [`return` statement](../statements/jump-statements.md#the-return-statement) or as an expression in an [expression-bodied member](lambda-operator.md#expression-body-definition). The following example shows the usage of the `default` literal: diff --git a/docs/csharp/language-reference/operators/index.md b/docs/csharp/language-reference/operators/index.md index 7fff6d38ca72a..9042b711779f0 100644 --- a/docs/csharp/language-reference/operators/index.md +++ b/docs/csharp/language-reference/operators/index.md @@ -32,7 +32,7 @@ In the following code, examples of expressions appear on the right-hand side of :::code language="csharp" source="snippets/shared/Overview.cs" id="Expressions"::: -Typically, an expression produces a result and can be included in another expression. A [`void`](../builtin-types/void.md) method call is an example of an expression that doesn't produce a result. It can be used only as a [statement](../../programming-guide/statements-expressions-operators/statements.md), as the following example shows: +Typically, an expression produces a result and can be included in another expression. A [`void`](../builtin-types/void.md) method call is an example of an expression that doesn't produce a result. It can be used only as a [statement](../../fundamentals/statements/index.md), as the following example shows: ```csharp Console.WriteLine("Hello, world!"); @@ -52,7 +52,7 @@ Here are some other kinds of expressions that C# provides: :::code language="csharp" source="snippets/shared/Overview.cs" id="Query"::: -You can use an [expression body definition](../../programming-guide/statements-expressions-operators/expression-bodied-members.md) to provide a concise definition for a method, constructor, property, indexer, or finalizer. +You can use an [expression body definition](lambda-operator.md#expression-body-definition) to provide a concise definition for a method, constructor, property, indexer, or finalizer. ## Operator precedence diff --git a/docs/csharp/language-reference/operators/lambda-operator.md b/docs/csharp/language-reference/operators/lambda-operator.md index 57faf7ac0c3b6..d79a2a58bfb5a 100644 --- a/docs/csharp/language-reference/operators/lambda-operator.md +++ b/docs/csharp/language-reference/operators/lambda-operator.md @@ -1,7 +1,8 @@ --- title: "The lambda operator - The `=>` operator is used to define a lambda expression" description: "The C# => operator defines lambda expressions and expression bodied members. Lambda expressions define a block of code used as data." -ms.date: 01/20/2026 +ms.date: 08/20/2026 +ai-usage: ai-assisted f1_keywords: - "=>_CSharpKeyword" helpviewer_keywords: @@ -41,15 +42,7 @@ An expression body definition uses the following general syntax: member => expression; ``` -The `expression` is a valid expression. The return type of `expression` must be implicitly convertible to the member's return type. If the member: - -- Has a `void` return type, or -- Is a: - - Constructor - - Finalizer - - Property or indexer `set` accessor - -`expression` must be a [*statement expression*](~/_csharpstandard/standard/statements.md#137-expression-statements). Because the expression's result is discarded, the return type of that expression can be any type. +For a member that returns a value, the expression's result must be implicitly convertible to the member's return type. For a `void` member, constructor, finalizer, or `set`, `init`, `add`, or `remove` accessor, the body must be a [*statement expression*](~/_csharpstandard/standard/statements.md#137-expression-statements). A statement expression can be an assignment, method invocation, object creation, increment or decrement operation, or `await` expression. Its result, if any, is discarded. The following example shows an expression body definition for a `Person.ToString` method: @@ -66,7 +59,13 @@ public override string ToString() } ``` -You can create expression body definitions for methods, operators, read-only properties, constructors, finalizers, and property and indexer accessors. For more information, see [Expression-bodied members](../../programming-guide/statements-expressions-operators/expression-bodied-members.md). +You can use expression body definitions for the following members: + +- **Methods and local functions:** A member that returns a value has the form `T M() => expression;`. A `void` member has the form `void M() => statementExpression;`. For more information, see [Methods](../../programming-guide/classes-and-structs/methods.md) and [Local functions](../../programming-guide/classes-and-structs/local-functions.md). +- **Operators:** An operator has the form `public static T operator +(T left, T right) => expression;`. For more information, see [Operator overloading](operator-overloading.md). +- **Properties and indexers:** A read-only property or indexer has the form `T P => expression;` or `T this[int i] => expression;`. You can also use expression bodies for individual accessors. A `get` accessor has the form `get => expression;`. A `set` or `init` accessor has the form `set => statementExpression;` or `init => statementExpression;`. For more information, see [Properties](../../programming-guide/classes-and-structs/properties.md) and [Indexers](../../programming-guide/indexers/index.md). +- **Constructors and finalizers:** These members have the form `C() => statementExpression;` or `~C() => statementExpression;`. For more information, see [Constructors](../../programming-guide/classes-and-structs/constructors.md) and [Finalizers](../../programming-guide/classes-and-structs/finalizers.md). +- **Event accessors:** An `add` or `remove` accessor has the form `add => statementExpression;` or `remove => statementExpression;`. For more information, see [Events](../../programming-guide/events/index.md). ## Operator overloadability diff --git a/docs/csharp/language-reference/operators/null-coalescing-operator.md b/docs/csharp/language-reference/operators/null-coalescing-operator.md index e3dd1c648d671..41776c0bc33f0 100644 --- a/docs/csharp/language-reference/operators/null-coalescing-operator.md +++ b/docs/csharp/language-reference/operators/null-coalescing-operator.md @@ -57,7 +57,7 @@ The `??` and `??=` operators are useful in the following scenarios: :::code language="csharp" source="snippets/shared/NullCoalescingOperator.cs" id="WithThrowExpression"::: - The preceding example also demonstrates how to use [expression-bodied members](../../programming-guide/statements-expressions-operators/expression-bodied-members.md) to define a property. + The preceding example also demonstrates how to use [expression-bodied members](lambda-operator.md#expression-body-definition) to define a property. - Use the `??=` operator to replace code of the following form: diff --git a/docs/csharp/language-reference/statements/selection-statements.md b/docs/csharp/language-reference/statements/selection-statements.md index db4e1b4ba1869..0de63a76555a1 100644 --- a/docs/csharp/language-reference/statements/selection-statements.md +++ b/docs/csharp/language-reference/statements/selection-statements.md @@ -77,7 +77,7 @@ In an expression context, you can use the [`switch` expression](../operators/swi > Differences between **switch expression** and **switch statement**: > > - **switch statement** is used to control the execution flow within a block of code. -> - **switch expression** is typically used in contexts of value return and value assignment, often as [expression-bodied members](../../programming-guide/statements-expressions-operators/expression-bodied-members.md). +> - **switch expression** is typically used in contexts of value return and value assignment, often as [expression-bodied members](../operators/lambda-operator.md#expression-body-definition). > - a **switch expression** case section can't be empty, but a **switch statement** case section can. ### Case guards diff --git a/docs/csharp/misc/cs1002.md b/docs/csharp/misc/cs1002.md index 4acbb699f5647..60fbeb3a0880e 100644 --- a/docs/csharp/misc/cs1002.md +++ b/docs/csharp/misc/cs1002.md @@ -1,18 +1,19 @@ --- description: "Compiler Error CS1002" title: "Compiler Error CS1002" -ms.date: 07/20/2015 +ms.date: 08/20/2026 f1_keywords: - "CS1002" helpviewer_keywords: - "CS1002" ms.assetid: 659b7abf-9311-40c9-9594-5372464c6148 +ai-usage: ai-assisted --- # Compiler Error CS1002 ; expected - The compiler detected a missing semicolon. A semicolon is required at the end of every statement in C#. A statement may span more than one line. + The compiler detected a missing semicolon. A semicolon terminates many C# statements, including declarations, assignments, expression statements, and `return` statements. Blocks and control statements such as `if`, `for`, and `while` don't end with a semicolon. A statement can span more than one line. The following sample generates CS1002: @@ -34,4 +35,4 @@ namespace x ## See also -- [Statements](../programming-guide/statements-expressions-operators/statements.md) +- [Statements](../fundamentals/statements/index.md) diff --git a/docs/csharp/programming-guide/classes-and-structs/constructors.md b/docs/csharp/programming-guide/classes-and-structs/constructors.md index 3c4273a08d237..e03c9b64e894f 100644 --- a/docs/csharp/programming-guide/classes-and-structs/constructors.md +++ b/docs/csharp/programming-guide/classes-and-structs/constructors.md @@ -32,7 +32,7 @@ A constructor is a method with the same name as its type. Its method signature c :::code source="./snippets/constructors/Program.cs" id="InstanceCtor"::: -If a constructor can be implemented as a single statement, you can use an [expression body member](../statements-expressions-operators/expression-bodied-members.md). The following example defines a `Location` class whose constructor has a single string parameter, `name`. The expression body definition assigns the argument to the `locationName` field. +If a constructor can be implemented as a single statement, you can use an [expression body member](../../language-reference/operators/lambda-operator.md#expression-body-definition). The following example defines a `Location` class whose constructor has a single string parameter, `name`. The expression body definition assigns the argument to the `locationName` field. :::code source="./snippets/constructors/Program.cs" id="ExpressionBodiedCtor"::: diff --git a/docs/csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md b/docs/csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md deleted file mode 100644 index 50539b28d58a4..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: "Expression-bodied members" -description: Learn about expression-bodied members. See code examples that use expression body definition for properties, constructors, finalizers, and more. -ms.date: 02/06/2019 -helpviewer_keywords: - - "expression-bodied members[C#]" - - "C# language, expression-bodied members" ---- -# Expression-bodied members (C# programming guide) - -Expression body definitions let you provide a member's implementation in a concise, readable form. You can use an expression body definition whenever the logic for any supported member, such as a method or property, consists of a single expression. An expression body definition has the following general syntax: - -```csharp -member => expression; -``` - -where *expression* is a valid expression. - -Expression body definitions can be used with the following type members: - -- [Method](#methods) -- [Read-only property](#read-only-properties) -- [Property](#properties) -- [Constructor](#constructors) -- [Finalizer](#finalizers) -- [Indexer](#indexers) - -## Methods - -An expression-bodied method consists of a single expression that returns a value whose type matches the method's return type, or, for methods that return `void`, that performs some operation. For example, types that override the method typically include a single expression that returns the string representation of the current object. - -The following example defines a `Person` class that overrides the method with an expression body definition. It also defines a `DisplayName` method that displays a name to the console. Additionally, it includes several methods that take parameters, demonstrating how expression-bodied members work with method parameters. The `return` keyword is not used in any of the expression body definitions. - -[!code-csharp[expression-bodied-methods](../../../../samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-methods.cs)] - -For more information, see [Methods (C# Programming Guide)](../classes-and-structs/methods.md). - -## Read-only properties - -You can use expression body definition to implement a read-only property. To do that, use the following syntax: - -```csharp -PropertyType PropertyName => expression; -``` - -The following example defines a `Location` class whose read-only `Name` property is implemented as an expression body definition that returns the value of the private `locationName` field: - -[!code-csharp[expression-bodied-read-only-property](../../../../samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-readonly.cs#1)] - -For more information about properties, see [Properties (C# Programming Guide)](../classes-and-structs/properties.md). - -## Properties - -You can use expression body definitions to implement property `get` and `set` accessors. The following example demonstrates how to do that: - -[!code-csharp[expression-bodied-property-get-set](../../../../samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-ctor.cs#1)] - -For more information about properties, see [Properties (C# Programming Guide)](../classes-and-structs/properties.md). - -## Events - -Similarly, event `add` and `remove` accessors can be expression-bodied: - -[!code-csharp[expression-bodied-event-add-remove](../../../../samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-event.cs#1)] - -For more information about events, see [Events (C# Programming Guide)](../events/index.md). - -## Constructors - -An expression body definition for a constructor typically consists of a single assignment expression or a method call that handles the constructor's arguments or initializes instance state. - -The following example defines a `Location` class whose constructor has a single string parameter named *name*. The expression body definition assigns the argument to the `Name` property. The example also shows a `Point` class with constructors that take multiple parameters, demonstrating how expression-bodied constructors work with different parameter combinations. - -[!code-csharp[expression-bodied-constructor](../../../../samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-ctor.cs#1)] - -For more information, see [Constructors (C# Programming Guide)](../classes-and-structs/constructors.md). - -## Finalizers - -An expression body definition for a finalizer typically contains cleanup statements, such as statements that release unmanaged resources. - -The following example defines a finalizer that uses an expression body definition to indicate that the finalizer has been called. - -[!code-csharp[expression-bodied-finalizer](../classes-and-structs/snippets/finalizers/expr-bodied-finalizer.cs#1)] - -For more information, see [Finalizers (C# Programming Guide)](../classes-and-structs/finalizers.md). - -## Indexers - -Like with properties, indexer `get` and `set` accessors consist of expression body definitions if the `get` accessor consists of a single expression that returns a value or the `set` accessor performs a simple assignment. - -The following example defines a class named `Sports` that includes an internal array that contains the names of some sports. Both the indexer `get` and `set` accessors are implemented as expression body definitions. - -[!code-csharp[expression-bodied-indexer](../../../../samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-indexers.cs#1)] - -For more information, see [Indexers (C# Programming Guide)](../indexers/index.md). - -## See also - -- [.NET code style rules for expression-bodied-members](../../../fundamentals/code-analysis/style-rules/language-rules.md#expression-bodied-members) diff --git a/docs/csharp/programming-guide/statements-expressions-operators/snippets/equality-comparisons/Program.cs b/docs/csharp/programming-guide/statements-expressions-operators/snippets/equality-comparisons/Program.cs new file mode 100644 index 0000000000000..7077333e3c014 --- /dev/null +++ b/docs/csharp/programming-guide/statements-expressions-operators/snippets/equality-comparisons/Program.cs @@ -0,0 +1,22 @@ +namespace EqualityComparisons; + +public static class Program +{ + public static void Main() + { + var first = new Sample { Number = 1, Text = "Hi" }; + var second = new Sample { Number = 1, Text = "Hi" }; + + Console.WriteLine(ReferenceEquals(first, second)); // => False + + second = first; + + Console.WriteLine(ReferenceEquals(first, second)); // => True + } + + private sealed class Sample + { + public int Number { get; init; } + public required string Text { get; init; } + } +} diff --git a/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.csproj b/docs/csharp/programming-guide/statements-expressions-operators/snippets/equality-comparisons/equality-comparisons.csproj similarity index 55% rename from samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.csproj rename to docs/csharp/programming-guide/statements-expressions-operators/snippets/equality-comparisons/equality-comparisons.csproj index a150dcce740be..bad583f080c8c 100644 --- a/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.csproj +++ b/docs/csharp/programming-guide/statements-expressions-operators/snippets/equality-comparisons/equality-comparisons.csproj @@ -1,11 +1,8 @@ - - + Exe - net8.0 - enable + net10.0 enable - Program + enable - diff --git a/docs/csharp/programming-guide/statements-expressions-operators/statements.md b/docs/csharp/programming-guide/statements-expressions-operators/statements.md deleted file mode 100644 index f264ad71d6b0a..0000000000000 --- a/docs/csharp/programming-guide/statements-expressions-operators/statements.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -title: "Statements" -description: Learn about statements in C# programming. See a list of statement types, and view code examples and additional resources. -ms.date: 07/20/2015 -helpviewer_keywords: - - "statements [C#], about statements" - - "C# language, statements" -ms.assetid: 901bcde7-87de-4e15-833c-f9cfd40c8ce3 ---- -# Statements (C# Programming Guide) - -The actions that a program takes are expressed in statements. Common actions include declaring variables, assigning values, calling methods, looping through collections, and branching to one or another block of code, depending on a given condition. The order in which statements are executed in a program is called the flow of control or flow of execution. The flow of control may vary every time that a program is run, depending on how the program reacts to input that it receives at run time. - -A statement can consist of a single line of code that ends in a semicolon, or a series of single-line statements in a block. A statement block is enclosed in {} brackets and can contain nested blocks. The following code shows two examples of single-line statements, and a multi-line statement block: - -[!code-csharp[csProgGuideStatements#1](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs#1)] - -## Types of statements - -The following table lists the various types of statements in C# and their associated keywords, with links to topics that include more information: - -|Category|C# keywords / notes| -|--------------|---------------------------| -|[Declaration statements](#declaration-statements)|A declaration statement introduces a new variable or constant. A variable declaration can optionally assign a value to the variable. In a constant declaration, the assignment is required.| -|[Expression statements](#expression-statements)|Expression statements that calculate a value must store the value in a variable.| -|Selection statements|Selection statements enable you to branch to different sections of code, depending on one or more specified conditions. For more information, see the following topics:
  • [if](../../language-reference/statements/selection-statements.md#the-if-statement)
  • [switch](../../language-reference/statements/selection-statements.md#the-switch-statement)
| -|Iteration statements|Iteration statements enable you to loop through collections like arrays, or perform the same set of statements repeatedly until a specified condition is met. For more information, see the following topics:
  • [do](../../language-reference/statements/iteration-statements.md#the-do-statement)
  • [for](../../language-reference/statements/iteration-statements.md#the-for-statement)
  • [foreach](../../language-reference/statements/iteration-statements.md#the-foreach-statement)
  • [while](../../language-reference/statements/iteration-statements.md#the-while-statement)
| -|Jump statements|Jump statements transfer control to another section of code. For more information, see the following topics:
  • [break](../../language-reference/statements/jump-statements.md#the-break-statement)
  • [continue](../../language-reference/statements/jump-statements.md#the-continue-statement)
  • [goto](../../language-reference/statements/jump-statements.md#the-goto-statement)
  • [return](../../language-reference/statements/jump-statements.md#the-return-statement)
  • [yield](../../language-reference/statements/yield.md)
| -|Exception-handling statements|Exception-handling statements enable you to gracefully recover from exceptional conditions that occur at run time. For more information, see the following topics:
  • [throw](../../language-reference/statements/exception-handling-statements.md#the-throw-statement)
  • [try-catch](../../language-reference/statements/exception-handling-statements.md#the-try-catch-statement)
  • [try-finally](../../language-reference/statements/exception-handling-statements.md#the-try-finally-statement)
  • [try-catch-finally](../../language-reference/statements/exception-handling-statements.md#the-try-catch-finally-statement)
| -|[`checked` and `unchecked`](../../language-reference/statements/checked-and-unchecked.md)|The `checked` and `unchecked` statements enable you to specify whether integral-type numerical operations are allowed to cause an overflow when the result is stored in a variable that is too small to hold the resulting value.| -|The `await` statement|If you mark a method with the [async](../../language-reference/keywords/async.md) modifier, you can use the [await](../../language-reference/operators/await.md) operator in the method. When control reaches an `await` expression in the async method, control returns to the caller, and progress in the method is suspended until the awaited task completes. When the task is complete, execution can resume in the method.

For a simple example, see the "Async Methods" section of [Methods](../classes-and-structs/methods.md). For more information, see [Asynchronous Programming with async and await](../../asynchronous-programming/index.md).| -|The `yield return` statement|An iterator performs a custom iteration over a collection, such as a list or an array. An iterator uses the [yield return](../../language-reference/statements/yield.md) statement to return each element one at a time. When a `yield return` statement is reached, the current location in code is remembered. Execution is restarted from that location when the iterator is called the next time.

For more information, see [Iterators](../concepts/iterators.md).| -|The `fixed` statement|The fixed statement prevents the garbage collector from relocating a movable variable. For more information, see [fixed](../../language-reference/statements/fixed.md).| -|The `lock` statement|The lock statement enables you to limit access to blocks of code to only one thread at a time. For more information, see [lock](../../language-reference/statements/lock.md).| -|Labeled statements|You can give a statement a label and then use the [goto](../../language-reference/statements/jump-statements.md#the-goto-statement) keyword to jump to the labeled statement. (See the example in the following row.)| -|The [empty statement](#the-empty-statement)|The empty statement consists of a single semicolon. It does nothing and can be used in places where a statement is required but no action needs to be performed.| - -## Declaration statements - -The following code shows examples of variable declarations with and without an initial assignment, and a constant declaration with the necessary initialization. - -[!code-csharp[csProgGuideStatements#23](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs#23)] - -## Expression statements - -The following code shows examples of expression statements, including assignment, object creation with assignment, and method invocation. - -[!code-csharp[csProgGuideStatements#24](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs#24)] - -## The empty statement - -The following examples show two uses for an empty statement: - -[!code-csharp[csProgGuideStatements#25](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs#25)] - -## Embedded statements - -Some statements, for example, [iteration statements](../../language-reference/statements/iteration-statements.md), always have an embedded statement that follows them. This embedded statement may be either a single statement or multiple statements enclosed by {} brackets in a statement block. Even single-line embedded statements can be enclosed in {} brackets, as shown in the following example: - -[!code-csharp[csProgGuideStatements#26](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs#26)] - -An embedded statement that is not enclosed in {} brackets cannot be a declaration statement or a labeled statement. This is shown in the following example: - -[!code-csharp[csProgGuideStatements#27](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs#27)] - -Put the embedded statement in a block to fix the error: - -[!code-csharp[csProgGuideStatements#28](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs#28)] - -## Nested statement blocks - -Statement blocks can be nested, as shown in the following code: - -[!code-csharp[csProgGuideStatements#29](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs#29)] - -## Unreachable statements - -If the compiler determines that the flow of control can never reach a particular statement under any circumstances, it will produce warning CS0162, as shown in the following example: - -[!code-csharp[csProgGuideStatements#22](~/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs#22)] - -## C# language specification - -For more information, see the [Statements](~/_csharpstandard/standard/statements.md) section of the [C# language specification](~/_csharpstandard/standard/README.md). - -## See also - -- [Statement keywords](../../language-reference/keywords/statement-keywords.md) -- [C# operators and expressions](../../language-reference/operators/index.md) diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index 194b6d7dd4156..ee0f9e535d015 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -122,6 +122,8 @@ items: - name: Operators displayName: "+, -, *, /, %, unary +, unary -, !, ++, --, <, >, <=, >=, ==, !=, &&, ||, ?:, =, +=, -=, *=, /=, %=" href: fundamentals/expressions/operators.md + - name: Statements overview + href: fundamentals/statements/index.md - name: Selection statements href: fundamentals/statements/selection.md - name: Iteration statements @@ -538,12 +540,14 @@ items: href: programming-guide/concepts/covariance-contravariance/using-variance-for-func-and-action-generic-delegates.md - name: Iterators href: programming-guide/concepts/iterators.md - - name: Statements, expressions, and equality - items: - - name: Statements - href: programming-guide/statements-expressions-operators/statements.md - - name: Expression-bodied members - href: programming-guide/statements-expressions-operators/expression-bodied-members.md + - name: Equality and equality comparisons + items: + - name: Equality comparisons + href: programming-guide/statements-expressions-operators/equality-comparisons.md + - name: "How to define value equality for a type" + href: programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md + - name: "How to test for reference equality (identity)" + href: programming-guide/statements-expressions-operators/how-to-test-for-reference-equality-identity.md - name: Types items: - name: Casting and Type Conversions diff --git a/docs/csharp/whats-new/csharp-version-history.md b/docs/csharp/whats-new/csharp-version-history.md index 00e829b2edcf8..f17e20d4a66ef 100644 --- a/docs/csharp/whats-new/csharp-version-history.md +++ b/docs/csharp/whats-new/csharp-version-history.md @@ -305,7 +305,7 @@ C# version 7.0 was released with Visual Studio 2017. This version has some evolu - [Tuples and deconstruction](../language-reference/builtin-types/value-tuples.md) - [Pattern matching](../fundamentals/functional/pattern-matching.md) - [Local functions](../programming-guide/classes-and-structs/local-functions.md) -- [Expanded expression bodied members](../programming-guide/statements-expressions-operators/expression-bodied-members.md) +- [Expanded expression bodied members](../language-reference/operators/lambda-operator.md#expression-body-definition) - [Ref locals](../language-reference/statements/declarations.md#reference-variables) - [Ref returns](../language-reference/statements/jump-statements.md#ref-returns) @@ -442,7 +442,7 @@ The major features of C# 1.0 included: - [Properties](../programming-guide/classes-and-structs/properties.md) - [Delegates](../delegates-overview.md) - [Operators and expressions](../language-reference/operators/index.md) -- [Statements](../programming-guide/statements-expressions-operators/statements.md) +- [Statements](../fundamentals/statements/index.md) - [Attributes](/dotnet/csharp/advanced-topics/reflection-and-attributes) _Article_ [_originally published on the NDepend blog_](https://blog.ndepend.com/c-versions-look-language-history/)_, courtesy of Erik Dietrich and Patrick Smacchia._ diff --git a/docs/fundamentals/code-analysis/style-rules/ide0021.md b/docs/fundamentals/code-analysis/style-rules/ide0021.md index 6636c82ed34e7..ba7872e4eac5a 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0021.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0021.md @@ -26,7 +26,7 @@ dev_langs: ## Overview -This style rule concerns the use of [expression bodies](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) versus block bodies for constructors. +This style rule concerns the use of [expression bodies](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) versus block bodies for constructors. ## Options @@ -80,6 +80,6 @@ For more information, see [How to suppress code analysis warnings](../suppress-w ## See also -- [Expression-bodied members](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) +- [Expression-bodied members](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) - [Code style language rules](language-rules.md) - [Code style rules reference](index.md) diff --git a/docs/fundamentals/code-analysis/style-rules/ide0022.md b/docs/fundamentals/code-analysis/style-rules/ide0022.md index c4943f87be289..af84a69b681ee 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0022.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0022.md @@ -26,7 +26,7 @@ dev_langs: ## Overview -This style rule concerns the use of [expression bodies](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) versus block bodies for methods. +This style rule concerns the use of [expression bodies](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) versus block bodies for methods. ## Options @@ -80,6 +80,6 @@ For more information, see [How to suppress code analysis warnings](../suppress-w ## See also -- [Expression-bodied members](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) +- [Expression-bodied members](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) - [Code style language rules](language-rules.md) - [Code style rules reference](index.md) diff --git a/docs/fundamentals/code-analysis/style-rules/ide0023-ide0024.md b/docs/fundamentals/code-analysis/style-rules/ide0023-ide0024.md index fdf32c95ef43d..e4352489813e1 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0023-ide0024.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0023-ide0024.md @@ -39,7 +39,7 @@ This article describes two related rules, `IDE0023` and `IDE0024`, which apply t ## Overview -This style rule concerns the use of [expression bodies](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) versus block bodies for operators. +This style rule concerns the use of [expression bodies](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) versus block bodies for operators. ## Options @@ -96,6 +96,6 @@ For more information, see [How to suppress code analysis warnings](../suppress-w ## See also -- [Expression-bodied members](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) +- [Expression-bodied members](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) - [Code style language rules](language-rules.md) - [Code style rules reference](index.md) diff --git a/docs/fundamentals/code-analysis/style-rules/ide0025.md b/docs/fundamentals/code-analysis/style-rules/ide0025.md index 7a1ffaac6507a..84deb50aa02de 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0025.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0025.md @@ -26,7 +26,7 @@ dev_langs: ## Overview -This style rule concerns the use of [expression bodies](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) versus block bodies for properties. +This style rule concerns the use of [expression bodies](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) versus block bodies for properties. ## Options @@ -138,6 +138,6 @@ For more information, see [How to suppress code analysis warnings](../suppress-w ## See also -- [Expression-bodied members](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) +- [Expression-bodied members](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) - [Code style language rules](language-rules.md) - [Code style rules reference](index.md) diff --git a/docs/fundamentals/code-analysis/style-rules/ide0026.md b/docs/fundamentals/code-analysis/style-rules/ide0026.md index 11a7d8663e1a8..f1623c4a7c535 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0026.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0026.md @@ -26,7 +26,7 @@ dev_langs: ## Overview -This style rule concerns the use of [expression bodies](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) versus block bodies for indexers. +This style rule concerns the use of [expression bodies](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) versus block bodies for indexers. ## Options @@ -80,6 +80,6 @@ For more information, see [How to suppress code analysis warnings](../suppress-w ## See also -- [Expression-bodied members](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) +- [Expression-bodied members](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) - [Code style language rules](language-rules.md) - [Code style rules reference](index.md) diff --git a/docs/fundamentals/code-analysis/style-rules/ide0027.md b/docs/fundamentals/code-analysis/style-rules/ide0027.md index 06e6e33990d58..bfebdd4f3373c 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0027.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0027.md @@ -26,7 +26,7 @@ dev_langs: ## Overview -This style rule concerns the use of [expression bodies](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) versus block bodies for accessors. +This style rule concerns the use of [expression bodies](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) versus block bodies for accessors. ## Options @@ -84,6 +84,6 @@ For more information, see [How to suppress code analysis warnings](../suppress-w ## See also -- [Expression-bodied members](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) +- [Expression-bodied members](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) - [Code style language rules](language-rules.md) - [Code style rules reference](index.md) diff --git a/docs/fundamentals/code-analysis/style-rules/ide0053.md b/docs/fundamentals/code-analysis/style-rules/ide0053.md index 7e84fe3c2f0ab..def3b63d6ed38 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0053.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0053.md @@ -26,7 +26,7 @@ dev_langs: ## Overview -This style rule concerns the use of [expression bodies](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) versus block bodies for [lambda expressions](../../../csharp/language-reference/operators/lambda-expressions.md). +This style rule concerns the use of [expression bodies](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) versus block bodies for [lambda expressions](../../../csharp/language-reference/operators/lambda-expressions.md). ## Options @@ -78,6 +78,6 @@ For more information, see [How to suppress code analysis warnings](../suppress-w ## See also -- [Expression-bodied members](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) +- [Expression-bodied members](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) - [Code style language rules](language-rules.md) - [Code style rules reference](index.md) diff --git a/docs/fundamentals/code-analysis/style-rules/ide0061.md b/docs/fundamentals/code-analysis/style-rules/ide0061.md index fec4444da6fee..e52beb636200b 100644 --- a/docs/fundamentals/code-analysis/style-rules/ide0061.md +++ b/docs/fundamentals/code-analysis/style-rules/ide0061.md @@ -26,7 +26,7 @@ dev_langs: ## Overview -This style rule concerns the use of [expression bodies](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) versus block bodies for [local functions](../../../csharp/programming-guide/classes-and-structs/local-functions.md). Local functions are private methods of a type that are nested in another member. +This style rule concerns the use of [expression bodies](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) versus block bodies for [local functions](../../../csharp/programming-guide/classes-and-structs/local-functions.md). Local functions are private methods of a type that are nested in another member. ## Options @@ -89,6 +89,6 @@ For more information, see [How to suppress code analysis warnings](../suppress-w ## See also -- [Expression-bodied members](../../../csharp/programming-guide/statements-expressions-operators/expression-bodied-members.md) +- [Expression-bodied members](../../../csharp/language-reference/operators/lambda-operator.md#expression-body-definition) - [Code style language rules](language-rules.md) - [Code style rules reference](index.md) diff --git a/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Program.cs b/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Program.cs deleted file mode 100644 index 1dac0be4c1200..0000000000000 --- a/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Program.cs +++ /dev/null @@ -1,14 +0,0 @@ -using CsCsrefProgrammingStatements; - -internal class Program -{ - private static void Main(string[] args) - { - SimpleStatements.Main(); - WrapStatements.Main(); - CsCsrefProgrammingStatements.WrapGuidelines.Test.Main(); - CsCsrefProgrammingStatements.ValueEquality.Program.Main(); - CsCsrefProgrammingStatements.ValueEquality.Program.Main(); - CsCsrefProgrammingStatements.ValueEqualityValueTypes.Program.Main(); - } -} diff --git a/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs b/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs deleted file mode 100644 index ef9652228ec6c..0000000000000 --- a/samples/snippets/csharp/VS_Snippets_VBCSharp/csProgGuideStatements/CS/Statements.cs +++ /dev/null @@ -1,638 +0,0 @@ -namespace CsCsrefProgrammingStatements -{ - //--------------------------------------------------------------------------- - public class SimpleStatements - { - // - public static void Main() - { - // Declaration statement. - int counter; - - // Assignment statement. - counter = 1; - - // Error! This is an expression, not an expression statement. - // counter + 1; - - // Declaration statements with initializers are functionally - // equivalent to declaration statement followed by assignment statement: - int[] radii = [15, 32, 108, 74, 9]; // Declare and initialize an array. - const double pi = 3.14159; // Declare and initialize constant. - - // foreach statement block that contains multiple statements. - foreach (int radius in radii) - { - // Declaration statement with initializer. - double circumference = pi * (2 * radius); - - // Expression statement (method invocation). A single-line - // statement can span multiple text lines because line breaks - // are treated as white space, which is ignored by the compiler. - System.Console.WriteLine($"Radius of circle #{counter} is {radius}. Circumference = {circumference:N2}"); - - // Expression statement (postfix increment). - counter++; - } // End of foreach statement block - } // End of Main method body. - } // End of SimpleStatements class. - /* - Output: - Radius of circle #1 = 15. Circumference = 94.25 - Radius of circle #2 = 32. Circumference = 201.06 - Radius of circle #3 = 108. Circumference = 678.58 - Radius of circle #4 = 74. Circumference = 464.96 - Radius of circle #5 = 9. Circumference = 56.55 - */ - // - public class WrapStatements - { - - public static void Main() - { - int x = 4; - bool b = ((x < 10) && (x > 5)) || ((x > 20) && (x < 25)); - bool b2 = 35 == System.Convert.ToInt32("35"); - } - - public static void test() - { - // - // Expression statements. - int i = 5; - string s = "Hello World"; - // - - System.Console.WriteLine(i.ToString()); - System.Console.WriteLine(s); - - // - int num = 5; - System.Console.WriteLine(num); // Output: 5 - num = 6; - System.Console.WriteLine(num); // Output: 6 - // - - int y = 0; - - // - y++; - // - - // - y = 2 + 3; - // - } - } - - //--------------------------------------------------------------------------- - namespace WrapGuidelines - { - // - using System; - class Test - { - public int Num { get; set; } - public string Str { get; set; } - - public static void Main() - { - Test a = new Test() { Num = 1, Str = "Hi" }; - Test b = new Test() { Num = 1, Str = "Hi" }; - - bool areEqual = System.Object.ReferenceEquals(a, b); - // False: - System.Console.WriteLine($"ReferenceEquals(a, b) = {areEqual}"); - - // Assign b to a. - b = a; - - // Repeat calls with different results. - areEqual = System.Object.ReferenceEquals(a, b); - // True: - System.Console.WriteLine($"ReferenceEquals(a, b) = {areEqual}"); - } - } - // - } - - // This is no longer in docs, replaced by ~docs\docs\csharp\programming-guide\statements-expressions-operators\snippets\how-to-define-value-equality-for-a-type\ValueEqualityClass\Program.cs - // - namespace ValueEquality - { - using System; - class TwoDPoint : IEquatable - { - // Readonly automatically implemented properties. - public int X { get; private set; } - public int Y { get; private set; } - - // Set the properties in the constructor. - public TwoDPoint(int x, int y) - { - if ((x < 1) || (x > 2000) || (y < 1) || (y > 2000)) - { - throw new System.ArgumentException("Point must be in range 1 - 2000"); - } - this.X = x; - this.Y = y; - } - - public override bool Equals(object obj) - { - return this.Equals(obj as TwoDPoint); - } - - public bool Equals(TwoDPoint p) - { - // If parameter is null, return false. - if (Object.ReferenceEquals(p, null)) - { - return false; - } - - // Optimization for a common success case. - if (Object.ReferenceEquals(this, p)) - { - return true; - } - - // If run-time types are not exactly the same, return false. - if (this.GetType() != p.GetType()) - { - return false; - } - - // Return true if the fields match. - // Note that the base class is not invoked because it is - // System.Object, which defines Equals as reference equality. - return (X == p.X) && (Y == p.Y); - } - - public override int GetHashCode() - { - return X * 0x00010000 + Y; - } - - public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs) - { - // Check for null on left side. - if (Object.ReferenceEquals(lhs, null)) - { - if (Object.ReferenceEquals(rhs, null)) - { - // null == null = true. - return true; - } - - // Only the left side is null. - return false; - } - // Equals handles case of null on right side. - return lhs.Equals(rhs); - } - - public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs) - { - return !(lhs == rhs); - } - } - - // For the sake of simplicity, assume a ThreeDPoint IS a TwoDPoint. - class ThreeDPoint : TwoDPoint, IEquatable - { - public int Z { get; private set; } - - public ThreeDPoint(int x, int y, int z) - : base(x, y) - { - if ((z < 1) || (z > 2000)) - { - throw new System.ArgumentException("Point must be in range 1 - 2000"); - } - this.Z = z; - } - - public override bool Equals(object obj) - { - return this.Equals(obj as ThreeDPoint); - } - - public bool Equals(ThreeDPoint p) - { - // If parameter is null, return false. - if (Object.ReferenceEquals(p, null)) - { - return false; - } - - // Optimization for a common success case. - if (Object.ReferenceEquals(this, p)) - { - return true; - } - - // Check properties that this class declares. - if (Z == p.Z) - { - // Let base class check its own fields - // and do the run-time type comparison. - return base.Equals((TwoDPoint)p); - } - else - { - return false; - } - } - - public override int GetHashCode() - { - return (X * 0x100000) + (Y * 0x1000) + Z; - } - - public static bool operator ==(ThreeDPoint lhs, ThreeDPoint rhs) - { - // Check for null. - if (Object.ReferenceEquals(lhs, null)) - { - if (Object.ReferenceEquals(rhs, null)) - { - // null == null = true. - return true; - } - - // Only the left side is null. - return false; - } - // Equals handles the case of null on right side. - return lhs.Equals(rhs); - } - - public static bool operator !=(ThreeDPoint lhs, ThreeDPoint rhs) - { - return !(lhs == rhs); - } - } - - class Program - { - public static void Main() - { - ThreeDPoint pointA = new ThreeDPoint(3, 4, 5); - ThreeDPoint pointB = new ThreeDPoint(3, 4, 5); - ThreeDPoint pointC = null; - int i = 5; - - Console.WriteLine($"pointA.Equals(pointB) = {pointA.Equals(pointB)}"); - Console.WriteLine($"pointA == pointB = {pointA == pointB}"); - Console.WriteLine($"null comparison = {pointA.Equals(pointC)}"); - Console.WriteLine($"Compare to some other type = {pointA.Equals(i)}"); - - TwoDPoint pointD = null; - TwoDPoint pointE = null; - - Console.WriteLine($"Two null TwoDPoints are equal: {pointD == pointE}"); - - pointE = new TwoDPoint(3, 4); - Console.WriteLine($"(pointE == pointA) = {pointE == pointA}"); - Console.WriteLine($"(pointA == pointE) = {pointA == pointE}"); - Console.WriteLine($"(pointA != pointE) = {pointA != pointE}"); - - System.Collections.ArrayList list = new System.Collections.ArrayList(); - list.Add(new ThreeDPoint(3, 4, 5)); - Console.WriteLine($"pointE.Equals(list[0]): {pointE.Equals(list[0])}"); - } - } - - /* Output: - pointA.Equals(pointB) = True - pointA == pointB = True - null comparison = False - Compare to some other type = False - Two null TwoDPoints are equal: True - (pointE == pointA) = False - (pointA == pointE) = False - (pointA != pointE) = True - pointE.Equals(list[0]): False - */ - } - // - - // Test the Hash Code -- this is not in docs - namespace ValueEquality - { - using System; - using System.Collections.Generic; - using System.Linq; - class Hash - { - public static void Main() - { - Random rand = new Random(); - List list = new List(); - for (int x = 0; x < 100000; x++) - { - list.Add(new ThreeDPoint(rand.Next(1, 2000), rand.Next(1, 2000), rand.Next(1, 2000))); - } - - list = (from item in list - select item) - .Distinct() - .ToList(); - - int uniqueObjects = list.Count(); - - Console.WriteLine($"there are {uniqueObjects} unique objects"); - - var uniqueHashCodes = (from item in list - select item.GetHashCode()) - .Distinct(); - - var hashCodeCount = uniqueHashCodes.Count(); - - // This only shows the number of unique values, not the evenness - // of their distribution. For that there is a LINQ query example in the docs - // how to group by a range. - Console.WriteLine($"there are {hashCodeCount} unique hash codes"); - - Console.WriteLine("Distribution:"); - - GroupByRange(uniqueHashCodes); - } - - static int GetRange(int hash, int granularity) - { - if (hash <= 0) - throw new System.ArgumentException("hash must be greater than 0", nameof(hash)); - return hash / (System.Int32.MaxValue / granularity); - } - - private static void GroupByRange(IEnumerable list) - { - Console.WriteLine("\r\nGroup by numeric range and project into a new anonymous type:"); - - var queryNumericRange = - from item in list - group item by GetRange(item, 100) into percentGroup - orderby percentGroup.Key - select percentGroup; - - // Nested foreach required to iterate over groups and group items. - foreach (var hashGroup in queryNumericRange) - { - Console.WriteLine($"Key: {(hashGroup.Key)} Count: {hashGroup.Count()}"); - } - } - } - } - - // This is no longer in docs, replaced by ~docs\docs\csharp\programming-guide\statements-expressions-operators\snippets\how-to-define-value-equality-for-a-type\ValueEqualityStruct\Program.cs - namespace ValueEqualityValueTypes - { - // - using System; - struct TwoDPoint : IEquatable - { - // Read/write automatically implemented properties. - public int X { get; private set; } - public int Y { get; private set; } - - public TwoDPoint(int x, int y) - : this() - { - X = x; - Y = x; - } - - public override bool Equals(object obj) - { - if (obj is TwoDPoint) - { - return this.Equals((TwoDPoint)obj); - } - return false; - } - - public bool Equals(TwoDPoint p) - { - return (X == p.X) && (Y == p.Y); - } - - public override int GetHashCode() - { - return X ^ Y; - } - - public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs) - { - return lhs.Equals(rhs); - } - - public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs) - { - return !(lhs.Equals(rhs)); - } - } - - class Program - { - public static void Main() - { - TwoDPoint pointA = new TwoDPoint(3, 4); - TwoDPoint pointB = new TwoDPoint(3, 4); - int i = 5; - - // Compare using virtual Equals, static Equals, and == and != operators. - // True: - Console.WriteLine($"pointA.Equals(pointB) = {pointA.Equals(pointB)}"); - // True: - Console.WriteLine($"pointA == pointB = {pointA == pointB}"); - // True: - Console.WriteLine($"object.Equals(pointA, pointB) = {object.Equals(pointA, pointB)}"); - // False: - Console.WriteLine($"pointA.Equals(null) = {pointA.Equals(null)}"); - // False: - Console.WriteLine($"(pointA == null) = {pointA == null}"); - // True: - Console.WriteLine($"(pointA != null) = {pointA != null}"); - // False: - Console.WriteLine($"pointA.Equals(i) = {pointA.Equals(i)}"); - // CS0019: - // Console.WriteLine($"pointA == i = {pointA == i}"); - - // Compare unboxed to boxed. - System.Collections.ArrayList list = new System.Collections.ArrayList(); - list.Add(new TwoDPoint(3, 4)); - // True: - Console.WriteLine($"pointA.Equals(list[0]): {pointA.Equals(list[0])}"); - - // Compare nullable to nullable and to non-nullable. - TwoDPoint? pointC = null; - TwoDPoint? pointD = null; - // False: - Console.WriteLine($"pointA == (pointC = null) = {pointA == pointC}"); - // True: - Console.WriteLine($"pointC == pointD = {pointC == pointD}"); - - TwoDPoint temp = new TwoDPoint(3, 4); - pointC = temp; - // True: - Console.WriteLine($"pointA == (pointC = 3,4) = {pointA == pointC}"); - - pointD = temp; - // True: - Console.WriteLine($"pointD == (pointC = 3,4) = {pointD == pointC}"); - } - } - - /* Output: - pointA.Equals(pointB) = True - pointA == pointB = True - Object.Equals(pointA, pointB) = True - pointA.Equals(null) = False - (pointA == null) = False - (pointA != null) = True - pointA.Equals(i) = False - pointE.Equals(list[0]): True - pointA == (pointC = null) = False - pointC == pointD = True - pointA == (pointC = 3,4) = True - pointD == (pointC = 3,4) = True - */ - } - // - - namespace WrapGuidelines2 - { - class Program - { - static void Test5() - { - /* Commented out to remove deliberate compile warning - // - // An over-simplified example of unreachable code. - const int val = 5; - if (val < 4) - { - System.Console.WriteLine("I'll never write anything."); //CS0162 - } - // - */ - } - - void TestMethod(string s) - { - // - // Variable declaration statements. - double area; - double radius = 2; - - // Constant declaration statement. - const double pi = 3.14159; - // - - // - // Expression statement (assignment). - area = 3.14 * (radius * radius); - - // Expression statement (result discarded). - int x = 0; - x++; - - // Expression statement (method invocation). - System.Console.WriteLine(); - - // Expression statement (new object creation). - System.Collections.Generic.List strings = - new System.Collections.Generic.List(); - // - - System.Console.WriteLine(pi.ToString()); - } - - bool GetNextMessage() { return true; } - - bool ProcessMessage() - { - if (GetNextMessage()) - { - // Code to process message... - return true; - } - else - { - return false; - } - } - bool done = false; - // - void ProcessMessages() - { - while (ProcessMessage()) - ; // Statement needed here. - } - - void F() - { - //... - if (done) goto exit; - //... - exit: - ; // Statement needed here. - } - // - - void G() - { - bool b = true; - // - // Recommended style. Embedded statement in block. - foreach (string s in System.IO.Directory.GetDirectories( - System.Environment.CurrentDirectory)) - { - System.Console.WriteLine(s); - } - - // Not recommended. - foreach (string s in System.IO.Directory.GetDirectories( - System.Environment.CurrentDirectory)) - System.Console.WriteLine(s); - // - - /* - // - if(pointB == true) - //Error CS1023: - int radius = 5; - // - */ - - // - if (b == true) - { - // OK: - System.DateTime d = System.DateTime.Now; - System.Console.WriteLine(d.ToLongDateString()); - } - // - } - string S() - { - // - foreach (string s in System.IO.Directory.GetDirectories( - System.Environment.CurrentDirectory)) - { - if (s.StartsWith("CSharp")) - { - if (s.EndsWith("TempFolder")) - { - return s; - } - } - } - return "Not found."; - // - } - } - } -} diff --git a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/Program.cs b/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/Program.cs deleted file mode 100644 index 3ae6c746e7ace..0000000000000 --- a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/Program.cs +++ /dev/null @@ -1,2 +0,0 @@ -// See https://aka.ms/new-console-template for more information -ExpressionBodiedMembers.Example.Main(); diff --git a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-ctor.cs b/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-ctor.cs deleted file mode 100644 index e43962db2811e..0000000000000 --- a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-ctor.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; - -namespace ExprBodied; - -// -public class Location -{ - private string locationName; - - public Location(string name) => Name = name; - - public string Name - { - get => locationName; - set => locationName = value; - } -} - -// Example with multiple parameters -public class Point -{ - public double X { get; } - public double Y { get; } - - // Constructor with multiple parameters - public Point(double x, double y) => (X, Y) = (x, y); - - // Constructor with single parameter (creates point at origin on axis) - public Point(double coordinate) => (X, Y) = (coordinate, 0); -} -// - -public class Example -{ - public static void Main() - { - var city = new Location("New York City"); - Console.WriteLine(city.Name); - - // Examples with multiple constructor parameters - var point1 = new Point(3.0, 4.0); - var point2 = new Point(5.0); - Console.WriteLine($"Point 1: ({point1.X}, {point1.Y})"); - Console.WriteLine($"Point 2: ({point2.X}, {point2.Y})"); - } -} \ No newline at end of file diff --git a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-event.cs b/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-event.cs deleted file mode 100644 index 8ca1ddf83c448..0000000000000 --- a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-event.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; - -namespace ExprBodied; - -// -public class ChangedEventArgs : EventArgs -{ - public required int NewValue { get; init; } -} - -public class ObservableNum(int _value) -{ - public event EventHandler ChangedGeneric = default!; - - public event EventHandler Changed - { - // Note that, while this is syntactically valid, it won't work as expected because it's creating a new delegate object with each call. - add => ChangedGeneric += (sender, args) => value(sender, args); - remove => ChangedGeneric -= (sender, args) => value(sender, args); - } - - public int Value - { - get => _value; - set => ChangedGeneric?.Invoke(this, new() { NewValue = (_value = value) }); - } -} -// - -public class ExpressionExample -{ - public static void Main() - { - void PrintingHandler(object? sender, object? args) - => Console.WriteLine((args as ChangedEventArgs)?.NewValue); - ObservableNum num = new(2); - num.Changed += PrintingHandler; - num.Value = 3; - num.Changed -= PrintingHandler; - num.Value = 1; // Still prints! - } -} diff --git a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-indexers.cs b/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-indexers.cs deleted file mode 100644 index f207b760870a2..0000000000000 --- a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-indexers.cs +++ /dev/null @@ -1,30 +0,0 @@ -// -using System; -using System.Collections.Generic; - -namespace SportsExample; - -public class Sports -{ - private string[] types = [ "Baseball", "Basketball", "Football", - "Hockey", "Soccer", "Tennis", - "Volleyball" ]; - - public string this[int i] - { - get => types[i]; - set => types[i] = value; - } -} -// - - class Program - { - static void Main() - { - var s = new Sports(); - Console.WriteLine(s[2]); - s[1] = "Softball"; - Console.WriteLine(s[1]); - } -} diff --git a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-methods.cs b/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-methods.cs deleted file mode 100644 index a11b3f27a94f9..0000000000000 --- a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-methods.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace ExpressionBodiedMembers; - -public class Person -{ - public Person(string firstName, string lastName) - { - fname = firstName; - lname = lastName; - } - - private string fname; - private string lname; - - public override string ToString() => $"{fname} {lname}".Trim(); - public void DisplayName() => Console.WriteLine(ToString()); - - // Expression-bodied methods with parameters - public string GetFullName(string title) => $"{title} {fname} {lname}"; - public int CalculateAge(int birthYear) => DateTime.Now.Year - birthYear; - public bool IsOlderThan(int age) => CalculateAge(1990) > age; - public string FormatName(string format) => format.Replace("{first}", fname).Replace("{last}", lname); -} - -class Example -{ - public static void Main() - { - Person p = new Person("Mandy", "Dejesus"); - Console.WriteLine(p); - p.DisplayName(); - - // Examples with parameters - Console.WriteLine(p.GetFullName("Dr.")); - Console.WriteLine($"Age: {p.CalculateAge(1990)}"); - Console.WriteLine($"Is older than 25: {p.IsOlderThan(25)}"); - Console.WriteLine(p.FormatName("Last: {last}, First: {first}")); - } -} diff --git a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-readonly.cs b/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-readonly.cs deleted file mode 100644 index ed49e29385697..0000000000000 --- a/samples/snippets/csharp/programming-guide/classes-and-structs/ExpressionBodiedMembers/expr-bodied-readonly.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace ExprBodiedReadonlyProperties; - -// -public class Location -{ - private string locationName; - - public Location(string name) - { - locationName = name; - } - - public string Name => locationName; -} -// - -public class Example -{ - public static void Main() - { - var city = new Location("New York City"); - Console.WriteLine(city.Name); - } -} From b7779bffad7a68afea19963ca7cf64092c87eff3 Mon Sep 17 00:00:00 2001 From: "azure-sdk-automation[bot]" <191533747+azure-sdk-automation[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 13:37:28 -0700 Subject: [PATCH 9/9] Update package index with latest published versions (#55627) Co-authored-by: azure-sdk --- docs/azure/includes/dotnet-all.md | 16 ++++++++-------- docs/azure/includes/dotnet-new.md | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/azure/includes/dotnet-all.md b/docs/azure/includes/dotnet-all.md index 5d24598094ee9..19f5980494af1 100644 --- a/docs/azure/includes/dotnet-all.md +++ b/docs/azure/includes/dotnet-all.md @@ -225,7 +225,7 @@ | Resource Management - Automanage | NuGet [1.1.2](https://www.nuget.org/packages/Azure.ResourceManager.Automanage/1.1.2) | [docs](/dotnet/api/overview/azure/ResourceManager.Automanage-readme) | GitHub [1.1.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Automanage_1.1.2/sdk/automanage/Azure.ResourceManager.Automanage/) | | Resource Management - Automation | NuGet [1.1.2](https://www.nuget.org/packages/Azure.ResourceManager.Automation/1.1.2) | [docs](/dotnet/api/overview/azure/ResourceManager.Automation-readme) | GitHub [1.1.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Automation_1.1.2/sdk/automation/Azure.ResourceManager.Automation/) | | Resource Management - Azure AI Search | NuGet [1.3.0](https://www.nuget.org/packages/Azure.ResourceManager.Search/1.3.0)
NuGet [1.4.0-beta.2](https://www.nuget.org/packages/Azure.ResourceManager.Search/1.4.0-beta.2) | [docs](/dotnet/api/overview/azure/ResourceManager.Search-readme) | GitHub [1.3.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Search_1.3.0/sdk/search/Azure.ResourceManager.Search/)
GitHub [1.4.0-beta.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Search_1.4.0-beta.2/sdk/search/Azure.ResourceManager.Search/) | -| Resource Management - Azure Stack HCI | NuGet [1.2.1](https://www.nuget.org/packages/Azure.ResourceManager.Hci/1.2.1)
NuGet [1.3.0-beta.2](https://www.nuget.org/packages/Azure.ResourceManager.Hci/1.3.0-beta.2) | [docs](/dotnet/api/overview/azure/ResourceManager.Hci-readme) | GitHub [1.2.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Hci_1.2.1/sdk/azurestackhci/Azure.ResourceManager.Hci/)
GitHub [1.3.0-beta.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Hci_1.3.0-beta.2/sdk/azurestackhci/Azure.ResourceManager.Hci/) | +| Resource Management - Azure Stack HCI | NuGet [1.3.0](https://www.nuget.org/packages/Azure.ResourceManager.Hci/1.3.0) | [docs](/dotnet/api/overview/azure/ResourceManager.Hci-readme) | GitHub [1.3.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Hci_1.3.0/sdk/azurestackhci/Azure.ResourceManager.Hci/) | | Resource Management - Azure VMware Solution | NuGet [1.6.1](https://www.nuget.org/packages/Azure.ResourceManager.Avs/1.6.1) | [docs](/dotnet/api/overview/azure/ResourceManager.Avs-readme) | GitHub [1.6.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Avs_1.6.1/sdk/avs/Azure.ResourceManager.Avs/) | | Resource Management - Batch | NuGet [1.7.0](https://www.nuget.org/packages/Azure.ResourceManager.Batch/1.7.0) | [docs](/dotnet/api/overview/azure/ResourceManager.Batch-readme) | GitHub [1.7.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Batch_1.7.0/sdk/batch/Azure.ResourceManager.Batch/) | | Resource Management - Billing | NuGet [1.2.2](https://www.nuget.org/packages/Azure.ResourceManager.Billing/1.2.2) | [docs](/dotnet/api/overview/azure/ResourceManager.Billing-readme) | GitHub [1.2.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Billing_1.2.2/sdk/billing/Azure.ResourceManager.Billing/) | @@ -459,13 +459,13 @@ | App Configuration Provider | NuGet [8.6.0](https://www.nuget.org/packages/Microsoft.Azure.AppConfiguration.AspNetCore/8.6.0) | | | | Azure Functions CLI | NuGet [5.0.0-preview.1](https://www.nuget.org/packages/Azure.Functions.Cli.Abstractions/5.0.0-preview.1) | | | | Azure Functions SDK | NuGet [0.5.0](https://www.nuget.org/packages/Azure.Functions.Sdk/0.5.0) | | | -| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp/1.0.0)
NuGet [3.0.0-beta.36](https://www.nuget.org/packages/Azure.Mcp/3.0.0-beta.36) | | | -| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp.linux-arm64/1.0.0)
NuGet [3.0.0-beta.36](https://www.nuget.org/packages/Azure.Mcp.linux-arm64/3.0.0-beta.36) | | | -| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp.linux-x64/1.0.0)
NuGet [3.0.0-beta.36](https://www.nuget.org/packages/Azure.Mcp.linux-x64/3.0.0-beta.36) | | | -| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp.osx-arm64/1.0.0)
NuGet [3.0.0-beta.36](https://www.nuget.org/packages/Azure.Mcp.osx-arm64/3.0.0-beta.36) | | | -| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp.osx-x64/1.0.0)
NuGet [3.0.0-beta.36](https://www.nuget.org/packages/Azure.Mcp.osx-x64/3.0.0-beta.36) | | | -| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp.win-arm64/1.0.0)
NuGet [3.0.0-beta.36](https://www.nuget.org/packages/Azure.Mcp.win-arm64/3.0.0-beta.36) | | | -| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp.win-x64/1.0.0)
NuGet [3.0.0-beta.36](https://www.nuget.org/packages/Azure.Mcp.win-x64/3.0.0-beta.36) | | | +| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp/1.0.0)
NuGet [3.0.0-beta.37](https://www.nuget.org/packages/Azure.Mcp/3.0.0-beta.37) | | | +| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp.linux-arm64/1.0.0)
NuGet [3.0.0-beta.37](https://www.nuget.org/packages/Azure.Mcp.linux-arm64/3.0.0-beta.37) | | | +| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp.linux-x64/1.0.0)
NuGet [3.0.0-beta.37](https://www.nuget.org/packages/Azure.Mcp.linux-x64/3.0.0-beta.37) | | | +| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp.osx-arm64/1.0.0)
NuGet [3.0.0-beta.37](https://www.nuget.org/packages/Azure.Mcp.osx-arm64/3.0.0-beta.37) | | | +| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp.osx-x64/1.0.0)
NuGet [3.0.0-beta.37](https://www.nuget.org/packages/Azure.Mcp.osx-x64/3.0.0-beta.37) | | | +| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp.win-arm64/1.0.0)
NuGet [3.0.0-beta.37](https://www.nuget.org/packages/Azure.Mcp.win-arm64/3.0.0-beta.37) | | | +| Azure MCP | NuGet [1.0.0](https://www.nuget.org/packages/Azure.Mcp.win-x64/1.0.0)
NuGet [3.0.0-beta.37](https://www.nuget.org/packages/Azure.Mcp.win-x64/3.0.0-beta.37) | | | | Azure MCP Types Internal | NuGet [0.2.804](https://www.nuget.org/packages/Microsoft.Azure.Mcp.AzTypes.Internal.Compact/0.2.804) | | | | Azure.Communication.Administration | NuGet [1.0.0-beta.3](https://www.nuget.org/packages/Azure.Communication.Administration/1.0.0-beta.3) | | | | Caching - PostgreSQL | NuGet [1.2.2](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Postgres/1.2.2) | | | diff --git a/docs/azure/includes/dotnet-new.md b/docs/azure/includes/dotnet-new.md index 4090a1d7aeb23..abc77de28b319 100644 --- a/docs/azure/includes/dotnet-new.md +++ b/docs/azure/includes/dotnet-new.md @@ -241,7 +241,7 @@ | Resource Management - Automanage | NuGet [1.1.2](https://www.nuget.org/packages/Azure.ResourceManager.Automanage/1.1.2) | [docs](/dotnet/api/overview/azure/ResourceManager.Automanage-readme) | GitHub [1.1.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Automanage_1.1.2/sdk/automanage/Azure.ResourceManager.Automanage/) | | Resource Management - Automation | NuGet [1.1.2](https://www.nuget.org/packages/Azure.ResourceManager.Automation/1.1.2) | [docs](/dotnet/api/overview/azure/ResourceManager.Automation-readme) | GitHub [1.1.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Automation_1.1.2/sdk/automation/Azure.ResourceManager.Automation/) | | Resource Management - Azure AI Search | NuGet [1.3.0](https://www.nuget.org/packages/Azure.ResourceManager.Search/1.3.0)
NuGet [1.4.0-beta.2](https://www.nuget.org/packages/Azure.ResourceManager.Search/1.4.0-beta.2) | [docs](/dotnet/api/overview/azure/ResourceManager.Search-readme) | GitHub [1.3.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Search_1.3.0/sdk/search/Azure.ResourceManager.Search/)
GitHub [1.4.0-beta.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Search_1.4.0-beta.2/sdk/search/Azure.ResourceManager.Search/) | -| Resource Management - Azure Stack HCI | NuGet [1.2.1](https://www.nuget.org/packages/Azure.ResourceManager.Hci/1.2.1)
NuGet [1.3.0-beta.2](https://www.nuget.org/packages/Azure.ResourceManager.Hci/1.3.0-beta.2) | [docs](/dotnet/api/overview/azure/ResourceManager.Hci-readme) | GitHub [1.2.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Hci_1.2.1/sdk/azurestackhci/Azure.ResourceManager.Hci/)
GitHub [1.3.0-beta.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Hci_1.3.0-beta.2/sdk/azurestackhci/Azure.ResourceManager.Hci/) | +| Resource Management - Azure Stack HCI | NuGet [1.3.0](https://www.nuget.org/packages/Azure.ResourceManager.Hci/1.3.0) | [docs](/dotnet/api/overview/azure/ResourceManager.Hci-readme) | GitHub [1.3.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Hci_1.3.0/sdk/azurestackhci/Azure.ResourceManager.Hci/) | | Resource Management - Azure VMware Solution | NuGet [1.6.1](https://www.nuget.org/packages/Azure.ResourceManager.Avs/1.6.1) | [docs](/dotnet/api/overview/azure/ResourceManager.Avs-readme) | GitHub [1.6.1](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Avs_1.6.1/sdk/avs/Azure.ResourceManager.Avs/) | | Resource Management - Batch | NuGet [1.7.0](https://www.nuget.org/packages/Azure.ResourceManager.Batch/1.7.0) | [docs](/dotnet/api/overview/azure/ResourceManager.Batch-readme) | GitHub [1.7.0](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Batch_1.7.0/sdk/batch/Azure.ResourceManager.Batch/) | | Resource Management - Billing | NuGet [1.2.2](https://www.nuget.org/packages/Azure.ResourceManager.Billing/1.2.2) | [docs](/dotnet/api/overview/azure/ResourceManager.Billing-readme) | GitHub [1.2.2](https://github.com/Azure/azure-sdk-for-net/tree/Azure.ResourceManager.Billing_1.2.2/sdk/billing/Azure.ResourceManager.Billing/) |