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

Record declaration syntax in C Sharp (C#) - Deep Dive

Choose your learning style9 modes available
Overview - Record declaration syntax
What is it?
A record in C# is a special kind of class designed to hold data with built-in features for immutability and value-based equality. Record declaration syntax is the way you define these records in your code. It looks similar to class declarations but includes keywords and structures that make working with data easier and safer. Records help you write less code when you want objects that mainly store values.
Why it matters
Without record declaration syntax, developers would write more code to handle data objects, especially for comparing, copying, and immutability. This syntax simplifies creating data containers that behave predictably, reducing bugs and improving readability. It makes programs easier to maintain and understand, especially when dealing with data that should not change after creation.
Where it fits
Before learning record declaration syntax, you should understand basic C# classes, properties, and constructors. After mastering records, you can explore advanced features like record inheritance, positional records, and with-expressions for copying records with modifications.
Mental Model
Core Idea
A record declaration is a concise way to define a data-focused object that treats its content as its identity.
Think of it like...
Think of a record like a labeled box that holds items. The box’s label (the record’s data) defines what the box is, not where it is stored. Two boxes with the same label are considered the same, no matter if they are different physical boxes.
┌───────────────┐
│   record      │
│  Person       │
│ ┌───────────┐ │
│ │ Name      │ │
│ │ Age       │ │
│ └───────────┘ │
└───────────────┘

Two records with same Name and Age are equal.
Build-Up - 7 Steps
1
FoundationBasic record declaration syntax
🤔
Concept: How to declare a simple record with properties.
In C#, you declare a record using the 'record' keyword followed by the name and curly braces. Inside, you define properties like in a class. Example: record Person { public string Name { get; init; } public int Age { get; init; } }
Result
You create a Person record type with two properties, Name and Age, that can be set only during initialization.
Understanding the 'record' keyword introduces a new type focused on data with built-in immutability.
2
FoundationPositional record syntax
🤔
Concept: Declaring records with a concise syntax that defines properties in the parameter list.
C# allows defining records with parameters directly in the declaration, which automatically creates properties and a constructor. Example: record Person(string Name, int Age);
Result
This creates a Person record with Name and Age properties and a constructor to set them.
Positional syntax reduces boilerplate and makes record declarations very concise.
3
IntermediateImmutability with init-only properties
🤔Before reading on: do you think record properties can be changed after creation? Commit to yes or no.
Concept: Records use 'init' accessors to make properties immutable after initialization.
Properties in records often use 'init' instead of 'set', meaning you can only assign values when creating the record or in an object initializer. Example: record Person { public string Name { get; init; } public int Age { get; init; } } var p = new Person { Name = "Alice", Age = 30 }; // p.Name = "Bob"; // Error: cannot assign after initialization
Result
Once a record instance is created, its properties cannot be changed, ensuring immutability.
Knowing that 'init' enforces immutability helps prevent accidental changes and bugs in data objects.
4
IntermediateValue-based equality in records
🤔Before reading on: do you think two record instances with same data are equal by default? Commit to yes or no.
Concept: Records override equality to compare data, not references.
Unlike classes, records compare their property values to determine equality. Example: var p1 = new Person("Alice", 30); var p2 = new Person("Alice", 30); bool areEqual = p1 == p2; // true
Result
Two records with identical data are considered equal, even if they are different objects in memory.
Understanding value-based equality is key to using records effectively for data comparison.
5
IntermediateUsing 'with' expressions to copy records
🤔Before reading on: do you think you can change a record’s property directly or must you create a new record? Commit to your answer.
Concept: 'with' expressions create a new record by copying an existing one with some changes.
Since records are immutable, you use 'with' to create modified copies. Example: var p1 = new Person("Alice", 30); var p2 = p1 with { Age = 31 };
Result
p2 is a new Person record with Age 31, while p1 remains unchanged.
Knowing how 'with' expressions work allows safe updates to immutable data.
6
AdvancedCustomizing record members and inheritance
🤔Before reading on: do you think records can inherit from other records and override methods? Commit to yes or no.
Concept: Records support inheritance and allow overriding methods like ToString and equality members.
You can create record hierarchies and customize behavior. Example: record Employee(string Name, int Age, string Position) : Person(Name, Age); You can override methods to change how records behave.
Result
Records can be extended and customized like classes but keep their data-focused nature.
Understanding inheritance in records helps design complex data models with shared behavior.
7
ExpertDeconstruction and compiler-generated methods
🤔Before reading on: do you think records automatically support deconstruction into variables? Commit to yes or no.
Concept: Records automatically generate methods like Deconstruct for easy unpacking of data.
You can unpack record properties into variables directly. Example: var person = new Person("Alice", 30); var (name, age) = person; This calls the compiler-generated Deconstruct method.
Result
Deconstruction simplifies working with record data in multiple variables.
Knowing about compiler-generated methods reveals how records reduce boilerplate and improve usability.
Under the Hood
When you declare a record, the C# compiler generates a class with special methods for equality, hashing, and string representation based on the record's properties. It also creates a constructor and Deconstruct method automatically. The 'init' accessor is implemented to allow property setting only during object initialization, enforcing immutability. The equality methods compare property values instead of object references, enabling value-based equality.
Why designed this way?
Records were introduced to simplify the common pattern of data containers that require immutability and value equality. Before records, developers had to write repetitive code for these features. The design balances conciseness, safety, and compatibility with existing class-based features. Alternatives like structs or tuples lacked flexibility or clarity, so records provide a clear, expressive syntax for data-focused types.
┌───────────────┐
│  Record Type  │
│  (Person)     │
├───────────────┤
│ Properties    │
│ - Name        │
│ - Age         │
├───────────────┤
│ Compiler-Gen  │
│ Methods:      │
│ - Constructor │
│ - Equals()    │
│ - GetHashCode()│
│ - ToString()  │
│ - Deconstruct()│
└───────────────┘

'init' allows setting only during creation.
Equality compares property values.
Myth Busters - 4 Common Misconceptions
Quick: Do two records with the same data always have the same reference in memory? Commit to yes or no.
Common Belief:Records are just like classes, so two records with the same data are different objects and not equal.
Tap to reveal reality
Reality:Records override equality to compare data, so two records with identical data are considered equal even if they are different objects.
Why it matters:Assuming records behave like classes can cause bugs when comparing data, leading to incorrect logic or duplicate data handling.
Quick: Can you change a record’s property after creating it? Commit to yes or no.
Common Belief:You can modify record properties anytime like normal class properties.
Tap to reveal reality
Reality:Record properties usually use 'init' accessors, so they can only be set during initialization and are immutable afterward.
Why it matters:Trying to modify properties later causes compile errors and misunderstanding immutability can lead to incorrect program design.
Quick: Does the 'with' expression modify the original record? Commit to yes or no.
Common Belief:'with' expressions change the existing record’s properties directly.
Tap to reveal reality
Reality:'with' creates a new record instance with specified changes, leaving the original unchanged.
Why it matters:Misusing 'with' can cause unexpected bugs by assuming original data changes, breaking immutability guarantees.
Quick: Are records always immutable? Commit to yes or no.
Common Belief:Records are always immutable by design.
Tap to reveal reality
Reality:Records can have mutable properties if you explicitly declare setters instead of 'init', but immutability is the recommended pattern.
Why it matters:Assuming all records are immutable can lead to unsafe code if mutable properties are introduced unknowingly.
Expert Zone
1
Records generate equality and hashing based on all positional parameters by default, but you can customize this by overriding methods or using non-positional properties.
2
The compiler-generated Deconstruct method matches the positional parameters, so adding non-positional properties requires manual deconstruction support.
3
Records support inheritance but only from other records, preserving value-based equality and immutability patterns across hierarchies.
When NOT to use
Avoid records when you need mutable objects with complex behavior or when identity-based equality is required. Use classes for mutable entities or when you need reference equality semantics.
Production Patterns
Records are widely used for data transfer objects (DTOs), configuration settings, and immutable domain models. They simplify serialization, comparison, and copying in APIs and business logic layers.
Connections
Immutable Data Structures
Records are a language feature that implements immutable data structures.
Understanding records helps grasp the broader concept of immutability, which improves program safety and concurrency.
Value Objects in Domain-Driven Design
Records naturally model value objects by focusing on data equality rather than identity.
Knowing record syntax aids in implementing domain models that follow best practices for value objects.
Mathematics: Tuples and Sets
Records behave like tuples with named fields and value equality, similar to mathematical tuples or sets where order and content define identity.
Seeing records as structured tuples connects programming data types to fundamental math concepts of grouping and equality.
Common Pitfalls
#1Trying to modify a record property after creation.
Wrong approach:var p = new Person("Alice", 30); p.Age = 31; // Error: cannot assign to 'Age' because it is init-only
Correct approach:var p = new Person("Alice", 30); var p2 = p with { Age = 31 };
Root cause:Misunderstanding that record properties with 'init' are immutable after initialization.
#2Assuming two records with same data are different when compared.
Wrong approach:var p1 = new Person("Alice", 30); var p2 = new Person("Alice", 30); bool equal = Object.ReferenceEquals(p1, p2); // false bool valueEqual = p1.Equals(p2); // true but ignored
Correct approach:var p1 = new Person("Alice", 30); var p2 = new Person("Alice", 30); bool equal = p1 == p2; // true
Root cause:Confusing reference equality with value equality in records.
#3Using mutable properties in records unintentionally.
Wrong approach:record Person { public string Name { get; set; } // mutable public int Age { get; set; } // mutable }
Correct approach:record Person { public string Name { get; init; } // immutable public int Age { get; init; } // immutable }
Root cause:Not using 'init' accessor leads to mutable records, breaking immutability assumptions.
Key Takeaways
Records in C# provide a concise syntax to create immutable, data-focused types with value-based equality.
Positional records allow defining properties and constructors in a single line, reducing boilerplate code.
The 'init' accessor enforces immutability by allowing property assignment only during object creation.
Records override equality to compare data, so two records with the same content are equal even if they are different objects.
'with' expressions create new records by copying existing ones with modifications, preserving immutability.