0
0
C Sharp (C#)programming~15 mins

Value equality in records in C Sharp (C#) - Deep Dive

Choose your learning style9 modes available
Overview - Value equality in records
What is it?
Value equality in records means that two record objects are considered equal if all their data values are the same, not just if they point to the same place in memory. Records in C# are special types designed to hold data with built-in support for comparing their contents easily. This makes it simple to check if two records represent the same information without writing extra code.
Why it matters
Without value equality, comparing two objects would only check if they are the exact same object in memory, which is often not what we want when dealing with data. Value equality lets us treat objects as equal based on their content, making programs more intuitive and less error-prone. This is especially important in scenarios like data transfer, caching, or testing where the actual data matters more than object identity.
Where it fits
Before learning value equality in records, you should understand basic C# classes, objects, and how equality works with reference types. After this, you can explore advanced topics like immutability, pattern matching with records, and how value equality affects collections and hashing.
Mental Model
Core Idea
Records compare their data values to decide equality, not their memory addresses.
Think of it like...
It's like two identical printed photos: even if they are different physical copies, they show the same picture, so we say they are equal in content.
┌───────────────┐       ┌───────────────┐
│ Record A      │       │ Record B      │
│ Name: "Alice" │       │ Name: "Alice" │
│ Age: 30       │       │ Age: 30       │
└──────┬────────┘       └──────┬────────┘
       │                        │
       │ Value equality compares│
       │ each field's content   │
       ▼                        ▼
    Equal? ─────────────────────► Yes
Build-Up - 7 Steps
1
FoundationUnderstanding reference equality
🤔
Concept: Learn how normal classes compare objects by their memory location.
In C#, when you create two objects of a class with the same data, comparing them with == or Equals checks if they are the same object in memory, not if their data matches. Example: class Person { public string Name; public int Age; } var p1 = new Person { Name = "Alice", Age = 30 }; var p2 = new Person { Name = "Alice", Age = 30 }; Console.WriteLine(p1 == p2); // false Console.WriteLine(p1.Equals(p2)); // false
Result
Output is false for both comparisons because p1 and p2 are different objects in memory.
Understanding that normal classes use reference equality explains why two objects with the same data are not equal by default.
2
FoundationIntroducing records and their purpose
🤔
Concept: Records are a new type in C# designed to hold data with built-in value equality.
Records are declared with the 'record' keyword and automatically provide value equality, meaning two records with the same data are equal. Example: record Person(string Name, int Age); var r1 = new Person("Alice", 30); var r2 = new Person("Alice", 30); Console.WriteLine(r1 == r2); // true Console.WriteLine(r1.Equals(r2)); // true
Result
Output is true for both comparisons because records compare their data values.
Knowing that records automatically compare data values simplifies equality checks and reduces boilerplate code.
3
IntermediateHow value equality works in records
🤔Before reading on: do you think records compare only the first field or all fields for equality? Commit to your answer.
Concept: Records compare all their properties and fields to determine equality.
When you compare two records, the compiler generates code that checks each property or field for equality. If all match, the records are equal. Example: record Person(string Name, int Age); var r1 = new Person("Alice", 30); var r2 = new Person("Alice", 31); Console.WriteLine(r1 == r2); // false because Age differs
Result
Output is false because the Age property is different, so the records are not equal.
Understanding that every field matters prevents mistakes assuming partial equality is enough.
4
IntermediateRecords and immutability
🤔Before reading on: do you think you can change a record's data after creation? Commit to your answer.
Concept: Records are immutable by default, meaning their data cannot be changed after creation, supporting reliable value equality.
By default, record properties are init-only, so you set them when creating the record but cannot modify them later. Example: record Person(string Name, int Age); var r = new Person("Alice", 30); r.Age = 31; // Error: cannot assign to init-only property
Result
Compilation error prevents changing data, ensuring the record's value stays consistent.
Knowing records are immutable helps maintain consistent equality and prevents bugs from unexpected changes.
5
IntermediateCustomizing equality in records
🤔Before reading on: do you think you can override how records compare equality? Commit to your answer.
Concept: You can override equality methods in records to customize how equality is determined.
Although records provide default value equality, you can override Equals and GetHashCode to change behavior. Example: record Person(string Name, int Age) { public virtual bool Equals(Person? other) { if (other is null) return false; return Name == other.Name; // Only compare Name } public override int GetHashCode() => Name.GetHashCode(); } var r1 = new Person("Alice", 30); var r2 = new Person("Alice", 31); Console.WriteLine(r1 == r2); // true because only Name is compared
Result
Output is true because equality is customized to ignore Age.
Knowing you can customize equality lets you adapt records to special cases beyond default behavior.
6
AdvancedRecords with inheritance and equality
🤔Before reading on: do you think derived records compare equality with base records? Commit to your answer.
Concept: Records support inheritance, but equality comparisons consider the exact runtime type to avoid false positives.
When comparing records in an inheritance chain, equality returns false if the types differ, even if data matches. Example: record Person(string Name); record Employee(string Name, int Id) : Person(Name); var p = new Person("Alice"); var e = new Employee("Alice", 1); Console.WriteLine(p == e); // false because types differ
Result
Output is false because Person and Employee are different types despite shared data.
Understanding type checks in equality prevents subtle bugs when using inheritance with records.
7
ExpertPerformance and hashing in record equality
🤔Before reading on: do you think record equality uses simple or complex hashing? Commit to your answer.
Concept: Records generate efficient hash codes combining all fields to support fast equality checks and dictionary lookups.
The compiler creates GetHashCode methods that combine hashes of all fields using algorithms like HashCode.Combine, balancing speed and collision resistance. This ensures that records work well as keys in dictionaries or sets. Example: record Person(string Name, int Age); var dict = new Dictionary(); dict[new Person("Alice", 30)] = "Data"; Console.WriteLine(dict.ContainsKey(new Person("Alice", 30))); // true
Result
Output is true because hashing and equality work together to find the key.
Knowing how hashing works under the hood explains why records perform well in collections and why all fields must be included.
Under the Hood
When you declare a record, the C# compiler automatically generates methods like Equals, GetHashCode, and == operator overloads. Equals compares each property or field using their own equality methods. GetHashCode combines the hash codes of all fields into one hash code using a standard algorithm. This ensures that two records with identical data produce the same hash code and are considered equal. The compiler also generates a Deconstruct method and supports with-expressions for immutability.
Why designed this way?
Records were introduced to simplify working with data objects by providing built-in value equality and immutability. Before records, developers had to write repetitive code to implement equality and hashing correctly, which was error-prone. The design balances ease of use, performance, and safety by generating code at compile time. Alternatives like classes with manual overrides were more verbose and less reliable.
┌───────────────┐
│   Record      │
│  Properties   │
└──────┬────────┘
       │
       ▼
┌─────────────────────┐
│ Compiler generates   │
│ Equals() method      │
│ GetHashCode() method │
│ == operator overload │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────────────┐
│ Runtime compares each field  │
│ and combines hashes for fast │
│ equality and dictionary use  │
└─────────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do two records with the same data but different types compare as equal? Commit to yes or no.
Common Belief:If two records have the same data, they are always equal regardless of their types.
Tap to reveal reality
Reality:Records compare both data and runtime types; different types mean they are not equal even if data matches.
Why it matters:Assuming different types are equal can cause bugs in collections or logic that rely on precise equality.
Quick: Does changing a property of a record after creation affect its equality? Commit to yes or no.
Common Belief:You can change record properties anytime, and equality will update accordingly.
Tap to reveal reality
Reality:Records are immutable by default; their properties cannot be changed after creation, preserving consistent equality.
Why it matters:Trying to mutate records breaks the immutability contract and can cause unexpected behavior or errors.
Quick: Does overriding Equals in a record always require overriding GetHashCode? Commit to yes or no.
Common Belief:You can override Equals without changing GetHashCode and still have correct behavior.
Tap to reveal reality
Reality:Overriding Equals without overriding GetHashCode breaks the hash code contract, causing incorrect behavior in hash-based collections.
Why it matters:Ignoring this leads to hard-to-find bugs when records are used as keys in dictionaries or sets.
Quick: Are records always slower than classes because of value equality? Commit to yes or no.
Common Belief:Value equality in records makes them slower than normal classes in all cases.
Tap to reveal reality
Reality:Records use optimized generated code for equality and hashing, often performing as well as or better than manual implementations.
Why it matters:Assuming records are slow may prevent developers from using them and missing out on safer, clearer code.
Expert Zone
1
Records use 'with-expressions' to create modified copies efficiently without breaking immutability.
2
Equality checks in records are shallow by default; nested reference types inside records require their own equality implementations to behave as expected.
3
Records support positional syntax and property syntax, which affects how equality and deconstruction work under the hood.
When NOT to use
Avoid records when you need mutable objects or complex inheritance hierarchies with behavior beyond data storage. Use classes when identity and mutable state are primary concerns. Also, for performance-critical scenarios where custom equality logic is needed, manual implementations might be better.
Production Patterns
Records are widely used for data transfer objects (DTOs), configuration settings, and immutable domain models. They simplify serialization, caching keys, and pattern matching in modern C# applications, especially in APIs and functional-style programming.
Connections
Structural typing in TypeScript
Both compare objects by their shape or data rather than identity.
Understanding value equality in records helps grasp how structural typing works by focusing on data compatibility over object identity.
Immutable data structures in functional programming
Records embody immutability and value equality principles common in functional languages.
Knowing records' immutability and equality clarifies how functional programming manages state and avoids side effects.
Legal contracts
Like records, contracts are considered equal if all terms match, regardless of who holds the paper copy.
This analogy shows how equality based on content rather than physical instance applies beyond programming, reinforcing the concept's universality.
Common Pitfalls
#1Trying to mutate a record's property after creation.
Wrong approach:var r = new Person("Alice", 30); r.Age = 31; // Error: cannot assign to init-only property
Correct approach:var r = new Person("Alice", 30); var r2 = r with { Age = 31 }; // Creates new record with changed Age
Root cause:Misunderstanding that records are immutable and require 'with' expressions to create modified copies.
#2Overriding Equals without overriding GetHashCode in a record.
Wrong approach:record Person(string Name) { public override bool Equals(object? obj) => obj is Person p && p.Name == Name; // Missing GetHashCode override }
Correct approach:record Person(string Name) { public override bool Equals(object? obj) => obj is Person p && p.Name == Name; public override int GetHashCode() => Name.GetHashCode(); }
Root cause:Not knowing the contract that equal objects must have the same hash code.
#3Assuming two records of different types with same data are equal.
Wrong approach:record Person(string Name); record Employee(string Name, int Id) : Person(Name); var p = new Person("Alice"); var e = new Employee("Alice", 1); Console.WriteLine(p == e); // Incorrectly expecting true
Correct approach:Console.WriteLine(p == e); // Correctly false because types differ
Root cause:Ignoring that record equality includes runtime type checks.
Key Takeaways
Records in C# provide built-in value equality by comparing all their data fields, not just object references.
They are immutable by default, ensuring consistent equality and safer code.
Equality in records also checks the runtime type, so different record types with the same data are not equal.
You can customize equality behavior by overriding methods, but must maintain the hash code contract.
Records are ideal for data-centric programming, simplifying comparisons, hashing, and immutability.