Abstract: This is a beginner’s tutorial on Records in C# with examples. C# Records are nothing more than C# language integrated support for the “Value Object” and “Immutable Object” patterns. They are compiled into regular “classes” and “structs” in assembly/IL.
Prerequisites
Please read up on the “Value Object” pattern and “Immutable Object” pattern since they are the motivation for introducing “records” into C# language. Here are some of my articles that are prerequisites for this tutorial:
Records in C# - Summary
I assume the reader is an intermediate-level C# programmer or upper and will go straight to the point, trying to demystify “Records”.
- What are C# “records” similar to? They are similar to regular classes or structs in C# language. When compiled into IL, they are compiled into regular classes or structs in IL. Records exist only in the imagination of the C# compiler and are not a new concept to IL.
- How are C# “records” different from regular classes or structs? Well, C# records are kind of “C# language integrated” support for the “Value Object” pattern and “Immutable Object” pattern. That means, for example, that the keyword “record” results in the automatic generation of several methods overloads (related to object equality) to give a class object “value semantics” ([5]).
- What do C# “records” bring to the table if we already have classes and structs in C#? The answer is that you can live without records in your code and use custom classes or structs to implement the “Value Object” and “Immutable Object” patterns, as before. But, since that is a new language feature, it is fancy to start using it. Also, it is advertised as helping programmers to achieve the same functionality in fewer lines of code.
- How do you define “records”? In the same place where you use “class” or “struct” keywords, you would use “record class” or “record struct”. The usage of just “record” is a synonym for “record class” since they first invented records for C#9 for classes, then they figured out they could make structs records in C#10.
- Are “records” immutable? Records are mutable in regular basic definition, as with regular classes and structs. But the typical use case of records might require making them immutable, and you can make them immutable just as any class or struct.
- Any other special syntax to mention? Yes, they invented a “Positional Syntax”, a one-liner constructor look-alike type definition for records. Still, in the background, a regular full-blown class or struct that implements their interpretation of the “Value (and sometime Immutable) Object” pattern is generated by the C# compiler.
- What about the new contextual “with” keyword? That keyword works with regular structs, too. When assigning structs, that keyword does “shallow clone” and mutates several properties of your choice. The industry name for that is “Nondestructive mutation”.
Of course, there are technical details on how to implement “records’ in code, but I thought the concept of what we are achieving and what problem we are trying to solve with records needs to be addressed first.
3 Utility for finding object Addresses
We developed a small utility that will give us the address of the objects in question, so by comparing addresses, it will be easily seen if we are talking about the same or different objects. The only problem is that our address-finding-utility has a limitation, that is, it works ONLY for objects on the heap that do not contain other objects on the heap (references). Therefore, we are forced to use only primitive values in our objects, which is why I needed to avoid using C# “string” and am using only “char” types. The code is in the attached projects.
4 Record Class Mutable - Example
Here we will give an example of a Record Class Mutable object and demo its behavior in typical situations, like mutation, assignment, and equality comparison.
4.1 Record Class Mutable- Decompiled
I used the decompiler tool dotPeek to decompile the assembly to see what the compiler is doing with the record. It has the option to create what they call “low-level-C#” from IL. So, what I did is “C# source”->assembly->IL->“low-level-C#”. Then I reduced all the trash info in that file and removed method contents for brevity. The result outlines the equivalent C# code generated by the C# compiler. That gives us a pretty good idea of what is happening behind the scenes and what the records are about.
5 Record Struct Mutable - Example
Here we will give an example of a Record Struct Mutable object and demo its behavior in typical situations, like mutation, assignment, and equality comparison.
5.1 Record Struct Mutable- Decompiled
I used the decompiler tool dotPeek to decompile the assembly to see what the compiler is doing with the record. It has the option to create what they call “low-level-C#” from IL. So, what I did is “C# source”->assembly->IL->“low-level-C#”. Then I reduced all the trash info in that file and removed method contents for brevity. The result outlines the equivalent C# code generated by the C# compiler. That gives us a pretty good idea of what is happening behind the scenes and what the records are about.
6 Record Class Immutable - Example
Here we will give an example of a Record Class Immutable object and demo its behavior in typical situations, like mutation, assignment, and equality comparison.
7 Record Struct Immutable - Example
Here we will give an example of a Record Struct Immutable object and demo its behavior in typical situations, like mutation, assignment, and equality comparison.
8 “Positional Syntax” for defining Records
They invented a “Positional Syntax”, a one-liner constructor look-alike type definition for records. Still, in the background, a regular full-blown class or struct that implements their interpretation of the “Value (and sometime Immutable) Object” pattern is generated by the C# compiler. The code generated is similar to the examples above, just an extra method, “Decontruct()” is added. Here is what this short notation looks like:
8.1 “Positional Syntax” for defining Records-Decompiled
I used the decompiler tool dotPeek to decompile the assembly to see what the compiler is doing with the record. It has the option to create what they call “low-level-C#” from IL. So, what I did is “C# source”->assembly->IL->“low-level-C#”. Then I reduced all the trash info in that file and removed method contents for brevity. The result outlines the equivalent C# code generated by the C# compiler. That gives us a pretty good idea of what is happening behind the scenes and what the records are about.
Here is what the above 3 “Positional Syntax” records generated code looks like.
9 Nondestructive mutation
If you want to reuse an Immutable record, you are free to reference it as often as you want because it is guaranteed not to change. But what if you want to reuse some of the data of an Immutable record but modify it a bit? That is why they invented “Nondestructive Mutation”. In C# language, now you can use the “with” keyword to do it. Typically, you would want to preserve most of the state of an Immutable record but change just some properties.
10 Deconstructing Records
As can be seen above, Records defined with “Positional Syntax” also have an automatically generated “Deconstruct()” method. It behaves as one would expect, similar to many other cases of “deconstruction” in C#.
Records that are not defined with “Positional Syntax” but with “record class” or “record struct” prefixes do not have the “Deconstruct()” method unless the programmer explicitly defines one.
Here are some examples:
11 Topics related to Records not covered
This is just a basic introduction to Records, and to keep this article manageable, some topics are not covered. They are:
12 Conclusion
C# Records are an interesting new feature of C#11, but all the concepts behind them have already been seen. C# Records are nothing more than C# language integrated support for the “Value Object” pattern ([8]) and the “Immutable Object” pattern ([7]). Records exist only in the imagination of the C# compiler and are compiled into regular “classes” and “structs”.
Integrating into C# language support for certain patterns is not new. For example, the “Observer pattern” ([6]) is integrated into C# via the usage of the Event mechanism.
Like any other new feature, their usage will propagate over time, and every modern C# programmer needs to know them well, if nothing else, because, over time, they will stumble upon code where they are used. And the last reason is that you need to learn and use Records, or otherwise you will be considered a “dinosaur C# programmer” that doesn’t know modern C#.
13 References
- Andrew Troelsen, Phil Japikse: Pro C# 10 with .NET 6, 11th Edition, 2022
- Joseph Albahari: C# 10 in a Nutshell, 2022
- https://en.wikipedia.org/wiki/Value_object
- https://en.wikipedia.org/wiki/Data_transfer_object
- https://en.wikipedia.org/wiki/Value_semantics
- https://www.codeproject.com/Articles/5326833/Observer-Pattern-in-Csharp
- https://www.codeproject.com/Articles/5353999/Csharp11-Immutable-Object-Pattern
- https://www.codeproject.com/Articles/5354124/Csharp-Value-Object-Pattern-Data-Transfer-Object-P