You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/csharp/programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md
+115-5Lines changed: 115 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,6 +3,7 @@ title: "How to define value equality for a class or struct"
3
3
description: Learn how to define value equality for a class or struct. See code examples and view available resources.
4
4
ms.topic: how-to
5
5
ms.date: 03/26/2021
6
+
ai-usage: ai-assisted
6
7
helpviewer_keywords:
7
8
- "overriding Equals method [C#]"
8
9
- "object equivalence [C#]"
@@ -56,14 +57,72 @@ The following example shows how records automatically implement value equality w
56
57
57
58
Records provide several advantages for value equality:
58
59
59
-
-**Automatic implementation**: Records automatically implement `IEquatable<T>` and override `Equals(object?)`, `GetHashCode()`, and the `==`/`!=` operators.
60
-
-**Correct inheritance behavior**: Unlike the class example shown earlier, records handle inheritance scenarios correctly.
60
+
-**Automatic implementation**: Records automatically implement <xref:System.IEquatable%601?displayProperty=nameWithType> and override <xref:System.Object.Equals%2A?displayProperty=nameWithType>, <xref:System.Object.GetHashCode%2A?displayProperty=nameWithType>, and the `==`/`!=` operators.
61
+
-**Correct inheritance behavior**: Records implement `IEquatable<T>` using virtual methods that check the runtime type of both operands, ensuring correct behavior in inheritance hierarchies and polymorphic scenarios.
61
62
-**Immutability by default**: Records encourage immutable design, which works well with value equality semantics.
62
63
-**Concise syntax**: Positional parameters provide a compact way to define data types.
63
64
-**Better performance**: The compiler-generated equality implementation is optimized and doesn't use reflection like the default struct implementation.
64
65
65
66
Use records when your primary goal is to store data and you need value equality semantics.
66
67
68
+
## Records with members that use reference equality
69
+
70
+
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 <xref:System.Collections.Generic.List%601?displayProperty=nameWithType>, arrays, and other reference types that don't implement value-based equality (with the notable exception of <xref:System.String?displayProperty=nameWithType>, which does implement value equality).
71
+
72
+
> [!IMPORTANT]
73
+
> 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 <xref:System.Collections.Generic.List%601?displayProperty=nameWithType>, <xref:System.Array?displayProperty=nameWithType>, 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.
> Thebuilt-invalueequalityof `record` typeshandlesscenarioslikethiscorrectly. If `TwoDPoint` and `ThreeDPoint` were `record` types, theresultof `p1.Equals(p2)` wouldbe `False`. Formoreinformation, see [Equalityin `record` typeinheritancehierarchies](../../language-reference/builtin-types/record.md#equality-in-inheritance-hierarchies).
Whenimplementingvalueequalityininheritancehierarchieswithclasses, thestandardapproachshownintheclassexample can lead to incorrect behavior when objects are used polymorphically. The issue occurs because <xref:System.IEquatable%601?displayProperty=nameWithType> implementations are chosen based on compile-time type, not runtime type.
150
+
151
+
### The problem with standard implementations
152
+
153
+
Consider this problematic scenario:
154
+
155
+
```csharp
156
+
TwoDPointp1 = newThreeDPoint(1, 2, 3); // Declared as TwoDPoint
157
+
TwoDPointp2=newThreeDPoint(1, 2, 4); // Declared as TwoDPoint
158
+
Console.WriteLine(p1.Equals(p2)); // True - but should be False!
159
+
```
160
+
161
+
The comparison returns `True` because the compiler selects `TwoDPoint.Equals(TwoDPoint)` based on the declared type, ignoring the `Z` coordinate differences.
162
+
163
+
The key to correct polymorphic equality is ensuring that all equality comparisons use the virtual <xref:System.Object.Equals%2A?displayProperty=nameWithType> method, which can check runtime types and handle inheritance correctly. This can be achieved by using explicit interface implementation for <xref:System.IEquatable%601?displayProperty=nameWithType> that delegates to the virtual method:
The preceding code demonstrates key elements to implementing value based equality:
186
+
187
+
-**Virtual `Equals(object?)` override**: The main equality logic happens in the virtual <xref:System.Object.Equals%2A?displayProperty=nameWithType> method, which is called regardless of compile-time type.
188
+
-**Runtime type checking**: Using `this.GetType() != p.GetType()` ensures that objects of different types are never considered equal.
189
+
-**Explicit interface implementation**: The <xref:System.IEquatable%601?displayProperty=nameWithType> implementation delegates to the virtual method, preventing compile-time type selection issues.
190
+
-**Protected virtual helper method**: The `protected virtual Equals(TwoDPoint? p)` method allows derived classes to override equality logic while maintaining type safety.
191
+
192
+
Use this pattern when:
193
+
194
+
- You have inheritance hierarchies where value equality is important
195
+
- Objects might be used polymorphically (declared as base type, instantiated as derived type)
196
+
- You need reference types with value equality semantics
197
+
198
+
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.
0 commit comments