From 1987a3f3aeba7c7a7dca0751e6458b4f65d8e91b Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 21 Jul 2026 11:56:35 -0400 Subject: [PATCH 01/12] [Everyday C#] Phase E, PR 14b: Type system: equality Add fundamentals concept article on object equality for classes, structs, records, and tuples. Covers value equality vs. reference equality, Equals/==/ReferenceEquals semantics, IEquatable implementation pattern, and record compiler-generated equality. Backed by a net10.0 snippet project (0 warnings, 0 errors). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5dca83ef-a274-4737-96d5-a311bfe58550 --- docs/csharp/fundamentals/types/equality.md | 113 ++++++++++++++++++ .../types/snippets/equality/Program.cs | 97 +++++++++++++++ .../types/snippets/equality/equality.csproj | 10 ++ docs/csharp/toc.yml | 2 + 4 files changed, 222 insertions(+) create mode 100644 docs/csharp/fundamentals/types/equality.md create mode 100644 docs/csharp/fundamentals/types/snippets/equality/Program.cs create mode 100644 docs/csharp/fundamentals/types/snippets/equality/equality.csproj diff --git a/docs/csharp/fundamentals/types/equality.md b/docs/csharp/fundamentals/types/equality.md new file mode 100644 index 0000000000000..403424401dccf --- /dev/null +++ b/docs/csharp/fundamentals/types/equality.md @@ -0,0 +1,113 @@ +--- +title: "Object equality in C#" +description: Learn how C# determines whether two values are equal. Understand value equality, reference equality, Equals, ==, ReferenceEquals, and IEquatable for classes, structs, records, and tuples. +ms.date: 07/21/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Object equality in C# + +> [!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 tests 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 and structs behave. In JavaScript, `===` on objects tests identity, like C# class defaults. + +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 is also called *identity*. Knowing which kind applies to a type 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 equality and reference equality + +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 — a pointer to an object stored on the managed heap. This distinction drives the default equality behavior: + +- **Built-in numeric types and enums** (value types): value equality — two `int` variables are equal when their numeric values match. +- **Structs** (value types): value equality — compares all fields. +- **Records** (`record class` and `record struct`): compiler-generated value equality — the compiler synthesizes , , and `==`/`!=` that compare every declared property. +- **Tuples** (value types): value equality — two tuples are equal when all their element values match. +- **Classes** (reference types): *reference equality* by default — `==` and test whether two variables point to the same object. + +`string` is a notable exception: even though `string` is a class, `==` and compare string content, not identity. + +The three tools for comparing values are: + +- `==` — the equality operator. Most types use this as the primary equality check. Its behavior depends on whether the type overrides it. +- — a virtual method inherited by every type. Can be overridden to change equality semantics. +- — a static method that always tests identity, regardless of any overrides. + +## Classes use reference equality by default + +A plain class inherits `==` and from . Both test identity — whether two variables refer to the same object. Two distinct objects with identical data are not equal by default: + +:::code language="csharp" source="snippets/equality/Program.cs" ID="ClassEquality"::: + +Assigning a class variable copies the reference, not the data. After `order3 = order1`, both variables point to the same object, so `==` returns `True`. + +## Structs use value equality + +A plain `struct` inherits from , which overrides the object-level equality with a field-by-field comparison. Two struct instances are equal when all their fields match: + +:::code language="csharp" source="snippets/equality/Program.cs" ID="StructEquality"::: + +Plain structs do not receive a `==` operator from the compiler. Use directly to compare structs, or implement to add typed equality and avoid the reflection overhead in the default implementation. + +## Records provide value equality automatically + +The `record` modifier generates value equality for both class-based and struct-based records. The compiler synthesizes , , and `==`/`!=` that compare every declared property value: + +:::code language="csharp" source="snippets/equality/Program.cs" ID="RecordEquality"::: + + confirms that `person1` and `person2` are different objects in memory, while `==` and return `True` because the compiler-generated equality compares property values. + +The same compiler generation applies to `record struct` types: + +:::code language="csharp" source="snippets/equality/Program.cs" ID="RecordStructEquality"::: + +Unlike a plain `struct`, a `record struct` generates `==` and `!=` operators in addition to . For more about record types and their equality semantics, see [Records](records.md). + +## Tuples use value equality + +C# tuples are value types. Two tuples are equal when every element value matches. Element names in a named tuple are a compile-time convenience and aren't considered during comparison — only positions and values matter: + +:::code language="csharp" source="snippets/equality/Program.cs" ID="TupleEquality"::: + +For more about tuple syntax and deconstruction, see [Tuples and deconstruction](tuples.md). + +## Add value equality to a class + +When a class represents a value — a color, a measurement, a currency amount — you want two instances with the same data to compare as equal. The simplest approach is the `record class` modifier, which generates all equality members automatically; see [Records](records.md). + +When you need a full class with value equality — for example, because the type has mutable state, a complex constructor, or can't be a record — implement . The interface requires a strongly typed `Equals(T?)` method that avoids boxing and provides the most efficient comparison path: + +:::code language="csharp" source="snippets/equality/Program.cs" ID="ColorDefinition"::: + +Implementing requires three members: + +- A typed `Equals(T?)` that compares the fields you care about. +- An `override` of `(object?)` that delegates to the typed version. +- 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. + +After implementing , `Equals` reflects value equality, but `==` still tests identity unless you also add operator overloads: + +:::code language="csharp" source="snippets/equality/Program.cs" ID="IEquatableUsage"::: + +Adding `==` and `!=` operators is a separate step. For details, see [Equality operators](../../language-reference/operators/equality-operators.md) in the language reference. + +## `Object.ReferenceEquals` — test identity directly + + always tests identity regardless of how a type overrides . Use it 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. + +## See also + +- [Type system overview](index.md) +- [Classes](classes.md) +- [Structs](structs.md) +- [Records](records.md) +- [Tuples and deconstruction](tuples.md) +- [Equality operators (language reference)](../../language-reference/operators/equality-operators.md) +- +- +- +- diff --git a/docs/csharp/fundamentals/types/snippets/equality/Program.cs b/docs/csharp/fundamentals/types/snippets/equality/Program.cs new file mode 100644 index 0000000000000..791c8b26b49b2 --- /dev/null +++ b/docs/csharp/fundamentals/types/snippets/equality/Program.cs @@ -0,0 +1,97 @@ +// +var order1 = new Order(42, "Shoes"); +var order2 = new Order(42, "Shoes"); + +Console.WriteLine(order1 == order2); // => False +Console.WriteLine(order1.Equals(order2)); // => False +Console.WriteLine(ReferenceEquals(order1, order2)); // => False + +Order order3 = order1; +Console.WriteLine(order1 == order3); // => True +// + +// +var pt1 = new Point(3, 4); +var pt2 = new Point(3, 4); + +Console.WriteLine(pt1.Equals(pt2)); // => True +// + +// +var person1 = new Person("Ada", "Lovelace"); +var person2 = new Person("Ada", "Lovelace"); + +Console.WriteLine(person1 == person2); // => True +Console.WriteLine(person1.Equals(person2)); // => True +Console.WriteLine(ReferenceEquals(person1, person2)); // => False +// + +// +var dim1 = new Dimension(1920, 1080); +var dim2 = new Dimension(1920, 1080); + +Console.WriteLine(dim1 == dim2); // => True +Console.WriteLine(dim1.Equals(dim2)); // => True +// + +// +var t1 = (Name: "Grace", Role: "Engineer"); +var t2 = (Name: "Grace", Role: "Engineer"); + +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 == override; identity check) +// + +// +var doc1 = new Document("Report"); +var doc2 = new Document("Report"); +var doc3 = doc1; + +Console.WriteLine(ReferenceEquals(doc1, doc2)); // => False +Console.WriteLine(ReferenceEquals(doc1, doc3)); // => True +// + +// ── Type declarations ──────────────────────────────────────────────────────── + +class Order(int id, string name) +{ + public int Id { get; } = id; + public string Name { get; } = name; +} + +struct Point(int x, int y) +{ + public int X { get; } = x; + public int Y { get; } = y; +} + +record Person(string First, string Last); + +record struct Dimension(double Width, double Height); + +// +sealed class Color(int r, int g, int b) : IEquatable +{ + public int R { get; } = r; + public int G { get; } = g; + public int B { get; } = b; + + public bool Equals(Color? other) => + other is not null && R == other.R && G == other.G && B == other.B; + + public override bool Equals(object? obj) => Equals(obj as Color); + public override int GetHashCode() => HashCode.Combine(R, G, B); +} +// + +class Document(string title) +{ + public string Title { get; } = title; +} diff --git a/docs/csharp/fundamentals/types/snippets/equality/equality.csproj b/docs/csharp/fundamentals/types/snippets/equality/equality.csproj new file mode 100644 index 0000000000000..dfb40caafcf9a --- /dev/null +++ b/docs/csharp/fundamentals/types/snippets/equality/equality.csproj @@ -0,0 +1,10 @@ + + + + Exe + net10.0 + enable + enable + + + diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index 511cb5c2df4a0..8bb82b0b27499 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -59,6 +59,8 @@ items: href: fundamentals/types/structs.md - name: Records href: fundamentals/types/records.md + - name: Equality + href: fundamentals/types/equality.md - name: Interfaces href: fundamentals/types/interfaces.md - name: Enumerations From cd5a57d770c2c3fe5d8a541ac57e52898649d231 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 21 Jul 2026 14:11:23 -0400 Subject: [PATCH 02/12] [Everyday C#] Move equality to expressions Relocate the Equality fundamentals article and snippets from Type system to the new Expressions folder, and update the TOC and relative links for the new location. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bc6b7895-3b18-45ff-99de-540618819cb3 --- .../{types => expressions}/equality.md | 16 ++++++++-------- .../snippets/equality/Program.cs | 0 .../snippets/equality/equality.csproj | 0 docs/csharp/toc.yml | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) rename docs/csharp/fundamentals/{types => expressions}/equality.md (96%) rename docs/csharp/fundamentals/{types => expressions}/snippets/equality/Program.cs (100%) rename docs/csharp/fundamentals/{types => expressions}/snippets/equality/equality.csproj (100%) diff --git a/docs/csharp/fundamentals/types/equality.md b/docs/csharp/fundamentals/expressions/equality.md similarity index 96% rename from docs/csharp/fundamentals/types/equality.md rename to docs/csharp/fundamentals/expressions/equality.md index 403424401dccf..d75f212b5083e 100644 --- a/docs/csharp/fundamentals/types/equality.md +++ b/docs/csharp/fundamentals/expressions/equality.md @@ -61,7 +61,7 @@ The same compiler generation applies to `record struct` types: :::code language="csharp" source="snippets/equality/Program.cs" ID="RecordStructEquality"::: -Unlike a plain `struct`, a `record struct` generates `==` and `!=` operators in addition to . For more about record types and their equality semantics, see [Records](records.md). +Unlike a plain `struct`, a `record struct` generates `==` and `!=` operators in addition to . For more about record types and their equality semantics, see [Records](../types/records.md). ## Tuples use value equality @@ -69,11 +69,11 @@ C# tuples are value types. Two tuples are equal when every element value matches :::code language="csharp" source="snippets/equality/Program.cs" ID="TupleEquality"::: -For more about tuple syntax and deconstruction, see [Tuples and deconstruction](tuples.md). +For more about tuple syntax and deconstruction, see [Tuples and deconstruction](../types/tuples.md). ## Add value equality to a class -When a class represents a value — a color, a measurement, a currency amount — you want two instances with the same data to compare as equal. The simplest approach is the `record class` modifier, which generates all equality members automatically; see [Records](records.md). +When a class represents a value — a color, a measurement, a currency amount — you want two instances with the same data to compare as equal. The simplest approach is the `record class` modifier, which generates all equality members automatically; see [Records](../types/records.md). When you need a full class with value equality — for example, because the type has mutable state, a complex constructor, or can't be a record — implement . The interface requires a strongly typed `Equals(T?)` method that avoids boxing and provides the most efficient comparison path: @@ -101,11 +101,11 @@ A common use is inside an `Equals` override to short-circuit the full comparison ## See also -- [Type system overview](index.md) -- [Classes](classes.md) -- [Structs](structs.md) -- [Records](records.md) -- [Tuples and deconstruction](tuples.md) +- [Type system overview](../types/index.md) +- [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) - - diff --git a/docs/csharp/fundamentals/types/snippets/equality/Program.cs b/docs/csharp/fundamentals/expressions/snippets/equality/Program.cs similarity index 100% rename from docs/csharp/fundamentals/types/snippets/equality/Program.cs rename to docs/csharp/fundamentals/expressions/snippets/equality/Program.cs diff --git a/docs/csharp/fundamentals/types/snippets/equality/equality.csproj b/docs/csharp/fundamentals/expressions/snippets/equality/equality.csproj similarity index 100% rename from docs/csharp/fundamentals/types/snippets/equality/equality.csproj rename to docs/csharp/fundamentals/expressions/snippets/equality/equality.csproj diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index 8bb82b0b27499..5284db7e654c5 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -59,8 +59,6 @@ items: href: fundamentals/types/structs.md - name: Records href: fundamentals/types/records.md - - name: Equality - href: fundamentals/types/equality.md - name: Interfaces href: fundamentals/types/interfaces.md - name: Enumerations @@ -117,6 +115,8 @@ items: href: fundamentals/tutorials/string-interpolation.md - name: Expressions and statements items: + - name: Equality + href: fundamentals/expressions/equality.md - name: Selection statements href: fundamentals/statements/selection.md - name: Iteration statements From 551279c21570b9d37edf300fb5e349a6e18075c3 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 21 Jul 2026 14:42:00 -0400 Subject: [PATCH 03/12] Address equality article review feedback Clarify default equality behavior, operator terminology, and manual value equality guidance. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bc6b7895-3b18-45ff-99de-540618819cb3 --- .../fundamentals/expressions/equality.md | 58 +++++++++++-------- .../expressions/snippets/equality/Program.cs | 2 +- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/docs/csharp/fundamentals/expressions/equality.md b/docs/csharp/fundamentals/expressions/equality.md index d75f212b5083e..62fee21374f55 100644 --- a/docs/csharp/fundamentals/expressions/equality.md +++ b/docs/csharp/fundamentals/expressions/equality.md @@ -1,6 +1,6 @@ --- title: "Object equality in C#" -description: Learn how C# determines whether two values are equal. Understand value equality, reference equality, Equals, ==, ReferenceEquals, and IEquatable for classes, structs, records, and tuples. +description: Learn how C# determines whether two values are equal. Understand value equality, reference equality, ==, !=, Equals, GetHashCode, ReferenceEquals, and IEquatable for classes, structs, records, and tuples. ms.date: 07/21/2026 ms.topic: concept-article ai-usage: ai-assisted @@ -11,31 +11,39 @@ 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 tests 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 and structs behave. In JavaScript, `===` on objects tests identity, like C# class defaults. +> **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 and structs behave. 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 is also called *identity*. Knowing which kind applies to a type 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 equality and reference equality -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 — a pointer to an object stored on the managed heap. This distinction drives the default equality behavior: +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 — a pointer to an object stored on the managed heap. 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). + +The default equality behavior follows the kind of type: - **Built-in numeric types and enums** (value types): value equality — two `int` variables are equal when their numeric values match. -- **Structs** (value types): value equality — compares all fields. -- **Records** (`record class` and `record struct`): compiler-generated value equality — the compiler synthesizes , , and `==`/`!=` that compare every declared property. +- **Structs** (value types): value equality through — all fields are compared, but plain structs don't get a predefined `==` operator. - **Tuples** (value types): value equality — two tuples are equal when all their element values match. -- **Classes** (reference types): *reference equality* by default — `==` and test whether two variables point to the same object. +- **Classes** (reference types): reference equality — `==` and test whether two variables point to the same object. +- **Interfaces** (reference types): reference equality for `==` — interface variables hold references. The call is dispatched to the underlying object's implementation. + +Some types change those defaults: -`string` is a notable exception: even though `string` is a class, `==` and compare string content, not identity. +- **Record classes** generate value equality even though they're reference types. **Record structs** also generate value equality and include `==`/`!=` operators. +- **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. -The three tools for comparing values are: +Equality is woven through these related members: -- `==` — the equality operator. Most types use this as the primary equality check. Its behavior depends on whether the type overrides it. -- — a virtual method inherited by every type. Can be overridden to change equality semantics. -- — a static method that always tests identity, regardless of any overrides. +- `==` — 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. Informally, both operands must be the same type. +- `!=` — the inequality operator. When a type defines a user-defined `==` operator, it must also define `!=`. +- — a virtual method inherited by every type. It can be overridden to change equality semantics. +- — a virtual method used by hash-based collections. When two values are equal, their hash codes must also be equal. +- — a static method that always tests identity, regardless of any `Equals` override or operator overload. ## Classes use reference equality by default -A plain class inherits `==` and from . Both test identity — whether two variables refer to the same object. Two distinct objects with identical data are not equal by default: +A plain class inherits `==` and from . Both test identity — whether two variables refer to the same object. Two distinct objects with identical data are not equal: :::code language="csharp" source="snippets/equality/Program.cs" ID="ClassEquality"::: @@ -47,7 +55,7 @@ A plain `struct` inherits from directly to compare structs, or implement to add typed equality and avoid the reflection overhead in the default implementation. +A plain `struct` inherits `Equals` from , which gives field-by-field value equality, but structs don't get a predefined `==` operator. Writing `p1 == p2` on a plain struct compiles only if the struct declares its own `operator ==`. Use directly to compare plain structs, or implement to add typed equality and avoid the reflection overhead in the default implementation. ## Records provide value equality automatically @@ -61,7 +69,7 @@ The same compiler generation applies to `record struct` types: :::code language="csharp" source="snippets/equality/Program.cs" ID="RecordStructEquality"::: -Unlike a plain `struct`, a `record struct` generates `==` and `!=` operators in addition to . For more about record types and their equality semantics, see [Records](../types/records.md). +Unlike a plain `struct`, a `record struct` generates `==` and `!=` operators in addition to . For more information about record types and their equality semantics, see [Records](../types/records.md). ## Tuples use value equality @@ -69,31 +77,31 @@ C# tuples are value types. Two tuples are equal when every element value matches :::code language="csharp" source="snippets/equality/Program.cs" ID="TupleEquality"::: -For more about tuple syntax and deconstruction, see [Tuples and deconstruction](../types/tuples.md). +For more information about tuple syntax and deconstruction, see [Tuples and deconstruction](../types/tuples.md). -## Add value equality to a class +## Add value equality to your own types -When a class represents a value — a color, a measurement, a currency amount — you want two instances with the same data to compare as equal. The simplest approach is the `record class` modifier, which generates all equality members automatically; see [Records](../types/records.md). +When a class or struct represents a value — a color, a measurement, a currency amount — you want two instances with the same data to compare as equal. Prefer `record class` or `record struct` first. Record types exist to generate value equality for you, including , , and `==`/`!=` operators. For more information about record types, see [Records](../types/records.md). -When you need a full class with value equality — for example, because the type has mutable state, a complex constructor, or can't be a record — implement . The interface requires a strongly typed `Equals(T?)` method that avoids boxing and provides the most efficient comparison path: +Use the manual path only when you must, such as when the type has mutable state, complex construction rules, or another constraint that prevents it from being a record. The interface requires one member: a strongly typed `Equals(T?)` method that avoids boxing and provides the most efficient comparison path: :::code language="csharp" source="snippets/equality/Program.cs" ID="ColorDefinition"::: -Implementing requires three members: +In practice, provide these related members together: -- A typed `Equals(T?)` that compares the fields you care about. -- An `override` of `(object?)` that delegates to the typed version. -- 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. +- The typed `Equals(T?)` required by . Compare the fields you care about. +- An `override` of that delegates to the typed version. This override isn't part of , but it keeps object-level equality consistent. +- An `override` of . This override isn't part of , but 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. -After implementing , `Equals` reflects value equality, but `==` still tests identity unless you also add operator overloads: +After implementing , `Equals` reflects value equality, but `==` still tests identity for classes unless you also add operator overloads. Plain structs still don't have a predefined `==` operator unless you declare one: :::code language="csharp" source="snippets/equality/Program.cs" ID="IEquatableUsage"::: -Adding `==` and `!=` operators is a separate step. For details, see [Equality operators](../../language-reference/operators/equality-operators.md) in the language reference. +Adding `==` and `!=` operators is a separate step, and the compiler requires them as a pair. For details, see [Equality operators](../../language-reference/operators/equality-operators.md) in the language reference. ## `Object.ReferenceEquals` — test identity directly - always tests identity regardless of how a type overrides . Use it when you need to confirm whether two variables point to the exact same object: + always tests identity regardless of how a type overrides or overloads `==`. Use it when you need to confirm whether two variables point to the exact same object: :::code language="csharp" source="snippets/equality/Program.cs" ID="ReferenceEqualsDemo"::: diff --git a/docs/csharp/fundamentals/expressions/snippets/equality/Program.cs b/docs/csharp/fundamentals/expressions/snippets/equality/Program.cs index 791c8b26b49b2..cb90695a00a98 100644 --- a/docs/csharp/fundamentals/expressions/snippets/equality/Program.cs +++ b/docs/csharp/fundamentals/expressions/snippets/equality/Program.cs @@ -46,7 +46,7 @@ var red2 = new Color(255, 0, 0); Console.WriteLine(red1.Equals(red2)); // => True -Console.WriteLine(red1 == red2); // => False (no == override; identity check) +Console.WriteLine(red1 == red2); // => False (no == overload; identity check) // // From 661d9e9f0a9e7c5087e37b73603c210c69587531 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 21 Jul 2026 14:44:27 -0400 Subject: [PATCH 04/12] Tighten struct equality guidance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bc6b7895-3b18-45ff-99de-540618819cb3 --- docs/csharp/fundamentals/expressions/equality.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/csharp/fundamentals/expressions/equality.md b/docs/csharp/fundamentals/expressions/equality.md index 62fee21374f55..38109f3b6e007 100644 --- a/docs/csharp/fundamentals/expressions/equality.md +++ b/docs/csharp/fundamentals/expressions/equality.md @@ -55,7 +55,7 @@ A plain `struct` inherits from , which gives field-by-field value equality, but structs don't get a predefined `==` operator. Writing `p1 == p2` on a plain struct compiles only if the struct declares its own `operator ==`. Use directly to compare plain structs, or implement to add typed equality and avoid the reflection overhead in the default implementation. +Structs don't get a predefined `==` operator. Writing `p1 == p2` on a plain struct compiles only if the struct declares its own `operator ==`. Use directly to compare plain structs, or implement to add typed equality and avoid the reflection overhead in the default implementation. ## Records provide value equality automatically From 279c039edba30419cf84f85ad581e3f3c5191f4f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 21 Jul 2026 14:56:25 -0400 Subject: [PATCH 05/12] Clarify manual equality guidance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bc6b7895-3b18-45ff-99de-540618819cb3 --- .../fundamentals/expressions/equality.md | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/csharp/fundamentals/expressions/equality.md b/docs/csharp/fundamentals/expressions/equality.md index 38109f3b6e007..e10e098129bd3 100644 --- a/docs/csharp/fundamentals/expressions/equality.md +++ b/docs/csharp/fundamentals/expressions/equality.md @@ -55,7 +55,7 @@ A plain `struct` inherits from directly to compare plain structs, or implement to add typed equality and avoid the reflection overhead in the default implementation. +Structs don't get a predefined `==` operator. Writing `p1 == p2` on a plain struct compiles only if the struct declares its own `operator ==`. A struct can declare `==`/`!=` without implementing , and it can implement without declaring those operators; the language treats those choices independently. For a complete, efficient value-type equality implementation, you typically provide both: a user-defined `==`/`!=` pair for operator comparisons and for typed equality that avoids the reflection overhead in the default implementation. Use directly when comparing plain structs that don't declare operators. ## Records provide value equality automatically @@ -69,7 +69,7 @@ The same compiler generation applies to `record struct` types: :::code language="csharp" source="snippets/equality/Program.cs" ID="RecordStructEquality"::: -Unlike a plain `struct`, a `record struct` generates `==` and `!=` operators in addition to . For more information about record types and their equality semantics, see [Records](../types/records.md). +Record types generate the whole equality set for their own type. Both `record class` and `record struct` types include a strongly typed `Equals(T?)` implementation, which means they implement , along with and . They also generate `==` and `!=` operators. 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). ## Tuples use value equality @@ -81,23 +81,27 @@ For more information about tuple syntax and deconstruction, see [Tuples and deco ## Add value equality to your own types -When a class or struct represents a value — a color, a measurement, a currency amount — you want two instances with the same data to compare as equal. Prefer `record class` or `record struct` first. Record types exist to generate value equality for you, including , , and `==`/`!=` operators. For more information about record types, see [Records](../types/records.md). +> [!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. -Use the manual path only when you must, such as when the type has mutable state, complex construction rules, or another constraint that prevents it from being a record. The interface requires one member: a strongly typed `Equals(T?)` method that avoids boxing and provides the most efficient comparison path: +When a class or struct represents a value — a color, a measurement, a currency amount — and it can't be a `record`, implement a consistent set of equality members. is only the typed part: the interface requires `Equals(T?)`, but a correct value-equality type provides all the related members so every equality path agrees. The language doesn't force the object override, hash code override, or operators, except that user-defined `==` and `!=` must be declared as a pair. -:::code language="csharp" source="snippets/equality/Program.cs" ID="ColorDefinition"::: +In a complete manual implementation, provide these members together: + +- `Equals(T?)`, declared by implementing . Compare the fields you care about. +- An `override` of that delegates to the typed version. This override 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. +- `==` and `!=` operators. Add them as a pair because the compiler requires a type that overloads one to overload the other. -In practice, provide these related members together: +The following example starts with the typed `Equals(T?)`, , and members so you can see their effect before operators are added: -- The typed `Equals(T?)` required by . Compare the fields you care about. -- An `override` of that delegates to the typed version. This override isn't part of , but it keeps object-level equality consistent. -- An `override` of . This override isn't part of , but 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. +:::code language="csharp" source="snippets/equality/Program.cs" ID="ColorDefinition"::: -After implementing , `Equals` reflects value equality, but `==` still tests identity for classes unless you also add operator overloads. Plain structs still don't have a predefined `==` operator unless you declare one: +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: :::code language="csharp" source="snippets/equality/Program.cs" ID="IEquatableUsage"::: -Adding `==` and `!=` operators is a separate step, and the compiler requires them as a pair. For details, see [Equality operators](../../language-reference/operators/equality-operators.md) in the language reference. +Adding `==` and `!=` operators completes the member set. For details, see [Equality operators](../../language-reference/operators/equality-operators.md) in the language reference. ## `Object.ReferenceEquals` — test identity directly From 017e4433432e58ac4277afadff57a133778955e4 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 21 Jul 2026 15:07:08 -0400 Subject: [PATCH 06/12] Deemphasize IEquatable Deemphasize the value of IEquatable throughout the article. --- .../fundamentals/expressions/equality.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/docs/csharp/fundamentals/expressions/equality.md b/docs/csharp/fundamentals/expressions/equality.md index e10e098129bd3..ae1a1fd1b12a9 100644 --- a/docs/csharp/fundamentals/expressions/equality.md +++ b/docs/csharp/fundamentals/expressions/equality.md @@ -6,7 +6,7 @@ ms.topic: concept-article ai-usage: ai-assisted --- -# Object equality in C# +# C# Equality comparisons > [!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. @@ -55,7 +55,7 @@ A plain `struct` inherits from , and it can implement without declaring those operators; the language treats those choices independently. For a complete, efficient value-type equality implementation, you typically provide both: a user-defined `==`/`!=` pair for operator comparisons and for typed equality that avoids the reflection overhead in the default implementation. Use directly when comparing plain structs that don't declare operators. +Structs don't get a predefined `==` operator. Writing `p1 == p2` on a plain struct compiles only if the struct declares its own `operator ==`. For a complete, efficient value-type equality implementation, you typically provide both: a user-defined `==`/`!=` pair for operator comparisons and for typed equality that avoids the reflection overhead in the default implementation. ## Records provide value equality automatically @@ -69,7 +69,7 @@ The same compiler generation applies to `record struct` types: :::code language="csharp" source="snippets/equality/Program.cs" ID="RecordStructEquality"::: -Record types generate the whole equality set for their own type. Both `record class` and `record struct` types include a strongly typed `Equals(T?)` implementation, which means they implement , along with and . They also generate `==` and `!=` operators. 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). +Record types generate the whole equality set for their own type. Both `record class` and `record struct` types include a strongly typed `Equals(T?)` implementation, which means they override and . They also generate `==` and `!=` operators. 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). ## Tuples use value equality @@ -84,14 +84,14 @@ For more information about tuple syntax and deconstruction, see [Tuples and deco > [!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 — a color, a measurement, a currency amount — and it can't be a `record`, implement a consistent set of equality members. is only the typed part: the interface requires `Equals(T?)`, but a correct value-equality type provides all the related members so every equality path agrees. The language doesn't force the object override, hash code override, or operators, except that user-defined `==` and `!=` must be declared as a pair. +When a class or struct represents a value — a color, a measurement, a currency amount — and it can't be a `record`, implement a consistent set of equality members. A correct value-equality type provides all the related members so every equality path agrees. The language enforces that user-defined `==` and `!=` must be declared as a pair. If you provide those, you'll get a warning if you don't also override -In a complete manual implementation, provide these members together: +In a complete manual implementation, provide these members: -- `Equals(T?)`, declared by implementing . Compare the fields you care about. +- `==` and `!=` operators. Add them as a pair because the compiler requires a type that overloads one to overload the other. - An `override` of that delegates to the typed version. This override 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. -- `==` and `!=` operators. Add them as a pair because the compiler requires a type that overloads one to overload the other. +- `Equals(T?)`, declared by implementing . Compare the fields you care about. The following example starts with the typed `Equals(T?)`, , and members so you can see their effect before operators are added: @@ -119,7 +119,3 @@ A common use is inside an `Equals` override to short-circuit the full comparison - [Records](../types/records.md) - [Tuples and deconstruction](../types/tuples.md) - [Equality operators (language reference)](../../language-reference/operators/equality-operators.md) -- -- -- -- From 56a85b015585fd3e1b6c230aa63f3b3e88733ec3 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 21 Jul 2026 15:11:55 -0400 Subject: [PATCH 07/12] Deemphasize IEquatable in equality article Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bc6b7895-3b18-45ff-99de-540618819cb3 --- docs/csharp/fundamentals/expressions/equality.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/csharp/fundamentals/expressions/equality.md b/docs/csharp/fundamentals/expressions/equality.md index ae1a1fd1b12a9..95318bcba2295 100644 --- a/docs/csharp/fundamentals/expressions/equality.md +++ b/docs/csharp/fundamentals/expressions/equality.md @@ -1,6 +1,6 @@ --- -title: "Object equality in C#" -description: Learn how C# determines whether two values are equal. Understand value equality, reference equality, ==, !=, Equals, GetHashCode, ReferenceEquals, and IEquatable for classes, structs, records, and tuples. +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/21/2026 ms.topic: concept-article ai-usage: ai-assisted @@ -69,7 +69,7 @@ The same compiler generation applies to `record struct` types: :::code language="csharp" source="snippets/equality/Program.cs" ID="RecordStructEquality"::: -Record types generate the whole equality set for their own type. Both `record class` and `record struct` types include a strongly typed `Equals(T?)` implementation, which means they override and . They also generate `==` and `!=` operators. 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). +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 strongly typed `Equals(T?)` implementation. 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). ## Tuples use value equality @@ -84,7 +84,7 @@ For more information about tuple syntax and deconstruction, see [Tuples and deco > [!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 — a color, a measurement, a currency amount — and it can't be a `record`, implement a consistent set of equality members. A correct value-equality type provides all the related members so every equality path agrees. The language enforces that user-defined `==` and `!=` must be declared as a pair. If you provide those, you'll get a warning if you don't also override +When a class or struct represents a value — a color, a measurement, a currency amount — and it can't be a `record`, implement a consistent set of equality members. A correct value-equality type provides all the related members so every equality path agrees. The language enforces that user-defined `==` and `!=` must be declared as a pair. If you provide those operators, the compiler warns you if you don't also override (CS0660) and (CS0661). In a complete manual implementation, provide these members: @@ -93,7 +93,7 @@ In a complete manual implementation, provide these members: - 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. - `Equals(T?)`, declared by implementing . Compare the fields you care about. -The following example starts with the typed `Equals(T?)`, , and members so you can see their effect before operators are added: +The following example starts with the and overrides, plus a typed `Equals(T?)` member, so you can see their effect before the `==` and `!=` operators are added: :::code language="csharp" source="snippets/equality/Program.cs" ID="ColorDefinition"::: From b4388d95e1fff190a43b69132849493df7e0449b Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 22 Jul 2026 11:06:36 -0400 Subject: [PATCH 08/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/csharp/fundamentals/expressions/equality.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/csharp/fundamentals/expressions/equality.md b/docs/csharp/fundamentals/expressions/equality.md index 95318bcba2295..4822a7ddfc995 100644 --- a/docs/csharp/fundamentals/expressions/equality.md +++ b/docs/csharp/fundamentals/expressions/equality.md @@ -35,7 +35,7 @@ Some types change those defaults: Equality is woven through these related members: -- `==` — 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. Informally, both operands must be the same type. +- `==` — 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. In general, the operands must be comparable; the compiler can apply built-in conversions (such as numeric promotions), and user-defined operators can compare different types. - `!=` — the inequality operator. When a type defines a user-defined `==` operator, it must also define `!=`. - — a virtual method inherited by every type. It can be overridden to change equality semantics. - — a virtual method used by hash-based collections. When two values are equal, their hash codes must also be equal. From 78e1cf347936c44f92d068fbb3c9a2464308e4f0 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 22 Jul 2026 11:31:47 -0400 Subject: [PATCH 09/12] Yet another editing pass Run another edit pass on this PR. --- .../fundamentals/expressions/equality.md | 53 ++++++++++--------- .../expressions/snippets/equality/Program.cs | 18 +++++-- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/docs/csharp/fundamentals/expressions/equality.md b/docs/csharp/fundamentals/expressions/equality.md index 4822a7ddfc995..375757683ab63 100644 --- a/docs/csharp/fundamentals/expressions/equality.md +++ b/docs/csharp/fundamentals/expressions/equality.md @@ -11,39 +11,38 @@ 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 and structs behave. +> **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 is also called *identity*. Knowing which kind applies to a type 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*. Knowing which kind applies to a type 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 equality and reference equality -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 — a pointer to an object stored on the managed heap. 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). +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). The default equality behavior follows the kind of type: -- **Built-in numeric types and enums** (value types): value equality — two `int` variables are equal when their numeric values match. -- **Structs** (value types): value equality through — all fields are compared, but plain structs don't get a predefined `==` operator. -- **Tuples** (value types): value equality — two tuples are equal when all their element values match. -- **Classes** (reference types): reference equality — `==` and test whether two variables point to the same object. -- **Interfaces** (reference types): reference equality for `==` — interface variables hold references. The call is dispatched to the underlying object's implementation. +- **Built-in numeric types and [enums](../types/enums.md)** (value types): value equality. Two `int` variables are equal when their numeric values match. +- **[Structs](../types/structs.md)** (value types): value equality through the inherited `Equals` method. All fields are compared, but plain structs don't get a predefined `==` operator. +- **[Tuples](../types/tuples.md)** (value types): value equality. Two tuples are equal when all their element values match. +- **[Classes](../types/classes.md)** (reference types): reference equality. `==` and test whether two variables point to the same object. Some types change those defaults: -- **Record classes** generate value equality even though they're reference types. **Record structs** also generate value equality and include `==`/`!=` operators. +- **[Record classes and record structs](../types/records.md#record-class-vs-record-struct)** generate value equality and include `==`/`!=` operators. - **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. Equality is woven through these related members: -- `==` — 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. In general, the operands must be comparable; the compiler can apply built-in conversions (such as numeric promotions), and user-defined operators can compare different types. -- `!=` — the inequality operator. When a type defines a user-defined `==` operator, it must also define `!=`. -- — a virtual method inherited by every type. It can be overridden to change equality semantics. -- — a virtual method used by hash-based collections. When two values are equal, their hash codes must also be equal. -- — a static method that always tests identity, regardless of any `Equals` override or operator overload. +- `==` - 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. +- `!=` - 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. +- - a virtual method used by hash-based collections. When two values are equal, their hash codes must also be equal. +- - a static method that always tests identity. ## Classes use reference equality by default -A plain class inherits `==` and from . Both test identity — whether two variables refer to the same object. Two distinct objects with identical data are not equal: +A plain class inherits `==` and from . Both test identity: whether two variables refer to the same object. Two distinct objects with identical data aren't equal: :::code language="csharp" source="snippets/equality/Program.cs" ID="ClassEquality"::: @@ -51,11 +50,11 @@ Assigning a class variable copies the reference, not the data. After `order3 = o ## Structs use value equality -A plain `struct` inherits from , which overrides the object-level equality with a field-by-field comparison. Two struct instances are equal when all their fields match: +A plain `struct` compares values when you call `Equals`. Two struct instances are equal when all their fields match: :::code language="csharp" source="snippets/equality/Program.cs" ID="StructEquality"::: -Structs don't get a predefined `==` operator. Writing `p1 == p2` on a plain struct compiles only if the struct declares its own `operator ==`. For a complete, efficient value-type equality implementation, you typically provide both: a user-defined `==`/`!=` pair for operator comparisons and for typed equality that avoids the reflection overhead in the default implementation. +Structs don't get a predefined `==` operator. Writing `p1 == p2` on a plain struct compiles only if the struct declares its own `operator ==`. If you need operator comparisons for a struct, define `==` and `!=` as a pair and keep them consistent with `Equals` and `GetHashCode`. ## Records provide value equality automatically @@ -69,11 +68,11 @@ The same compiler generation applies to `record struct` types: :::code language="csharp" source="snippets/equality/Program.cs" ID="RecordStructEquality"::: -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 strongly typed `Equals(T?)` implementation. 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). +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). ## Tuples use value equality -C# tuples are value types. Two tuples are equal when every element value matches. Element names in a named tuple are a compile-time convenience and aren't considered during comparison — only positions and values matter: +C# tuples are value types. Two tuples are equal when every element value matches. Element names in a named tuple are a compile-time convenience and aren't considered during comparison. Only positions and values matter: :::code language="csharp" source="snippets/equality/Program.cs" ID="TupleEquality"::: @@ -82,18 +81,18 @@ For more information about tuple syntax and deconstruction, see [Tuples and deco ## Add value equality to your own types > [!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. +> 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 — a color, a measurement, a currency amount — and it can't be a `record`, implement a consistent set of equality members. A correct value-equality type provides all the related members so every equality path agrees. The language enforces that user-defined `==` and `!=` must be declared as a pair. If you provide those operators, the compiler warns you if you don't also override (CS0660) and (CS0661). +When a class or struct represents a value, such as a color or a measurement, you must implement a consistent set of equality members. A correct value-equality type provides all the related members so every equality path agrees. The easiest way to achieve this consistency is to declare the type as a `record`. If you must derive from some non-record class, you need to implement these methods 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 that delegates to the typed version. This override keeps object-level equality consistent. +- An `override` of . This override 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. -- `Equals(T?)`, declared by implementing . Compare the fields you care about. +- 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 a typed `Equals(T?)` member, so you can see their effect before the `==` and `!=` operators are added: +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"::: @@ -101,7 +100,7 @@ At this point, `Equals` reflects value equality, but `==` still tests identity f :::code language="csharp" source="snippets/equality/Program.cs" ID="IEquatableUsage"::: -Adding `==` and `!=` operators completes the member set. For details, see [Equality operators](../../language-reference/operators/equality-operators.md) in the language reference. +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. ## `Object.ReferenceEquals` — test identity directly @@ -111,6 +110,9 @@ Adding `==` and `!=` operators completes the member set. For details, see [Equal 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. + ## See also - [Type system overview](../types/index.md) @@ -119,3 +121,4 @@ A common use is inside an `Equals` override to short-circuit the full comparison - [Records](../types/records.md) - [Tuples and deconstruction](../types/tuples.md) - [Equality operators (language reference)](../../language-reference/operators/equality-operators.md) + diff --git a/docs/csharp/fundamentals/expressions/snippets/equality/Program.cs b/docs/csharp/fundamentals/expressions/snippets/equality/Program.cs index cb90695a00a98..670765639cd50 100644 --- a/docs/csharp/fundamentals/expressions/snippets/equality/Program.cs +++ b/docs/csharp/fundamentals/expressions/snippets/equality/Program.cs @@ -77,16 +77,23 @@ record Person(string First, string Last); record struct Dimension(double Width, double Height); // -sealed class Color(int r, int g, int b) : IEquatable +class Color : IEquatable { - public int R { get; } = r; - public int G { get; } = g; - public int B { get; } = b; + 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) => Equals(obj as Color); + public override bool Equals(object? obj) => obj is Color other && Equals(other); public override int GetHashCode() => HashCode.Combine(R, G, B); } // @@ -95,3 +102,4 @@ class Document(string title) { public string Title { get; } = title; } + From 27ba8f22af9e85535d0d05f1b17b452468c3b135 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 22 Jul 2026 11:35:46 -0400 Subject: [PATCH 10/12] lint --- docs/csharp/fundamentals/expressions/equality.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/csharp/fundamentals/expressions/equality.md b/docs/csharp/fundamentals/expressions/equality.md index 375757683ab63..ca064fd65bc70 100644 --- a/docs/csharp/fundamentals/expressions/equality.md +++ b/docs/csharp/fundamentals/expressions/equality.md @@ -121,4 +121,3 @@ A common use is inside an `Equals` override to short-circuit the full comparison - [Records](../types/records.md) - [Tuples and deconstruction](../types/tuples.md) - [Equality operators (language reference)](../../language-reference/operators/equality-operators.md) - From 159c07696011e7b007342130f45df4a6bd9acc55 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 22 Jul 2026 14:29:00 -0400 Subject: [PATCH 11/12] Restructure for a better organization This organization of the material works much better. --- .../fundamentals/expressions/equality.md | 80 +++++++++---------- 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/docs/csharp/fundamentals/expressions/equality.md b/docs/csharp/fundamentals/expressions/equality.md index ca064fd65bc70..533a7e9852deb 100644 --- a/docs/csharp/fundamentals/expressions/equality.md +++ b/docs/csharp/fundamentals/expressions/equality.md @@ -1,7 +1,7 @@ --- 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/21/2026 +ms.date: 07/22/2026 ms.topic: concept-article ai-usage: ai-assisted --- @@ -13,52 +13,58 @@ ai-usage: ai-assisted > > **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*. Knowing which kind applies to a type 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*. 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. -## Value equality and reference equality +## 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). -The default equality behavior follows the kind of type: +The default equality behavior usually follows the kind of type: -- **Built-in numeric types and [enums](../types/enums.md)** (value types): value equality. Two `int` variables are equal when their numeric values match. -- **[Structs](../types/structs.md)** (value types): value equality through the inherited `Equals` method. All fields are compared, but plain structs don't get a predefined `==` operator. -- **[Tuples](../types/tuples.md)** (value types): value equality. Two tuples are equal when all their element values match. -- **[Classes](../types/classes.md)** (reference types): reference equality. `==` and test whether two variables point to the same object. +- **Built-in numeric types and [enums](../types/enums.md)** are value types. Two `int` variables are equal when their numeric values match. +- **[Structs](../types/structs.md)** are value types. A plain `struct` uses value equality when you call . +- **[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. -Some types change those defaults: +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: -- **[Record classes and record structs](../types/records.md#record-class-vs-record-struct)** generate value equality and include `==`/`!=` operators. -- **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. +:::code language="csharp" source="snippets/equality/Program.cs" ID="ClassEquality"::: -Equality is woven through these related members: +A plain `struct` shows value equality through . Two struct instances are equal when their fields match: -- `==` - 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. -- `!=` - 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. -- - a virtual method used by hash-based collections. When two values are equal, their hash codes must also be equal. -- - a static method that always tests identity. +:::code language="csharp" source="snippets/equality/Program.cs" ID="StructEquality"::: -## Classes use reference equality by default +Plain structs don't get a predefined `==` operator. Writing `p1 == p2` on a plain struct compiles only if the struct declares its own `operator ==`. If you need operator comparisons for a struct, define `==` and `!=` as a pair and keep them consistent with and . -A plain class inherits `==` and from . Both test identity: whether two variables refer to the same object. Two distinct objects with identical data aren't equal: +Tuples are value types too. Two tuples are equal when every element value matches. Element names in a named tuple are a compile-time convenience and aren't considered during comparison. Only positions and values matter: -:::code language="csharp" source="snippets/equality/Program.cs" ID="ClassEquality"::: +:::code language="csharp" source="snippets/equality/Program.cs" ID="TupleEquality"::: -Assigning a class variable copies the reference, not the data. After `order3 = order1`, both variables point to the same object, so `==` returns `True`. +For more information about tuple syntax and deconstruction, see [Tuples and deconstruction](../types/tuples.md). -## Structs use value equality +## Types can define different equality semantics -A plain `struct` compares values when you call `Equals`. Two struct instances are equal when all their fields match: +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. -:::code language="csharp" source="snippets/equality/Program.cs" ID="StructEquality"::: +Common exceptions and customizations include: -Structs don't get a predefined `==` operator. Writing `p1 == p2` on a plain struct compiles only if the struct declares its own `operator ==`. If you need operator comparisons for a struct, define `==` and `!=` as a pair and keep them consistent with `Equals` and `GetHashCode`. +- **[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. -## Records provide value equality automatically +Equality is woven through these related members: + +- `==` - 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. +- `!=` - 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. +- - a static method that always tests identity. + +## Use records for value equality -The `record` modifier generates value equality for both class-based and struct-based records. The compiler synthesizes , , and `==`/`!=` that compare every declared property value: +Use the `record` modifier to give a data-focused type value equality when the type can be a record. The compiler generates , , and `==`/`!=` members that compare every declared property value. + +A `record class` is still a reference type, but it compares values instead of identity: :::code language="csharp" source="snippets/equality/Program.cs" ID="RecordEquality"::: @@ -70,25 +76,17 @@ 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). -## Tuples use value equality - -C# tuples are value types. Two tuples are equal when every element value matches. Element names in a named tuple are a compile-time convenience and aren't considered during comparison. Only positions and values matter: - -:::code language="csharp" source="snippets/equality/Program.cs" ID="TupleEquality"::: - -For more information about tuple syntax and deconstruction, see [Tuples and deconstruction](../types/tuples.md). - -## Add value equality to your own types +## 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, you must implement a consistent set of equality members. A correct value-equality type provides all the related members so every equality path agrees. The easiest way to achieve this consistency is to declare the type as a `record`. If you must derive from some non-record class, you need to implement these methods 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. +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 keeps object-level equality consistent. +- 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. @@ -102,9 +100,9 @@ At this point, `Equals` reflects value equality, but `==` still tests identity f 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. -## `Object.ReferenceEquals` — test identity directly +## Use `Object.ReferenceEquals` to test identity directly - always tests identity regardless of how a type overrides or overloads `==`. Use it when you need to confirm whether two variables point to the exact same object: + 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"::: From 0e0cf8fd59724430caed2cc2c9f92e974eb6bc19 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 23 Jul 2026 11:59:57 -0400 Subject: [PATCH 12/12] Apply suggestions from code review Co-authored-by: Wade Pickett --- docs/csharp/fundamentals/expressions/equality.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/csharp/fundamentals/expressions/equality.md b/docs/csharp/fundamentals/expressions/equality.md index 533a7e9852deb..efbb6325469d7 100644 --- a/docs/csharp/fundamentals/expressions/equality.md +++ b/docs/csharp/fundamentals/expressions/equality.md @@ -11,7 +11,7 @@ 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. @@ -54,11 +54,11 @@ Common exceptions and customizations include: Equality is woven through these related members: -- `==` - 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. -- `!=` - 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. -- - a static method that always tests identity. +- `==`: 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. +- `!=`: 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. +- : a static method that always tests identity. ## Use records for value equality @@ -81,7 +81,7 @@ Record types generate the whole equality set for their own type. Both `record cl > [!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. +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: