Record Data Type And Collections In C# 9.0

Hello everyone, this time, I came up with a small topic related to using Record data types in C# 9.0 together with collections.What we realized, C# 9.0 gives us a data structure perfectly suitable for Immutable Objects.**Immutable Objects are not new; they exist in Java, C#, Python, TypeScript, React.JS, and many other languages and frameworks. Why people use Immutable Objects** is clear, they provide some significant advantages in a particular context:
- Eliminate unexpected side effects in behavior when working with shared objects, including shared among threads;
- Maintain a consistent state.
Perhaps the main disadvantages of using Immutable Objects are:
- In performance-critical systems, Immutable Objects may lead to higher data allocation.
- Require more coding for equality comparers and property modifications instantiating new objects.
If you use Immutable Objects in C# and see it helpful in your case, the new Record data type should help with the last point of the disadvantages list by reducing the amount of code you should typically write.**Let’s take a concrete example with a simple blog post model and see how a typical class, not record, would look.
`public class Post { public string Body { get; }
public Post(string body) { Body = body; }
protected bool Equals(Post other) { return Body == other.Body; }
public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; return Equals((Post) obj); }
public override int GetHashCode() { return (Body != null ? Body.GetHashCode() : 0); } }`
As we see from the code above, it’s quite an overhead to generate one property and compare objects by their content with equality members’ help.
With C# 9.0 and the new Records** data type, the equality members are not necessary anymore because the compiler does that job for us automatically. Let’s see how an equivalent code looks like:
`public record Post { public string Body { get; } public Post(string body) => Body = body; }`
t looks significantly simpler, cleaner and provides value comparison logic out of the box. Just have a look at the disassembled code, and you’ll see that the .net compiler generates equality members for us behind the scene.
Adding a new property is fairly easy, no need to bother about a huge boilerplate of code.
At first glance, it’s all good unless you start working with collections. Once you add a property, say, a collection of tags, assigned to the post:
public ReadOnlyCollection
It stops working out of the box as ReadOnlyCollection or ImmutableList – whatever you prefer – is a reference type and is compared accordingly by reference, not values. As of now, there is no data type you could use directly from the framework.
A pretty simple solution exists though, you might want to create a wrapper around ReadOnlyCollection and implement equality members as you wish.
`public class RecordsCollection
public RecordsCollection(IList
public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; }
if (ReferenceEquals(this, obj)) { return true; }
return obj is RecordsCollection
public override int GetHashCode() { var hashCode = new HashCode(); foreach (T item in Items) { hashCode.Add(item); }
return hashCode.ToHashCode(); } }`
In this example, equality is defined by the sequence of the elements. Therefore the final Post record would work as expected and look simple and clear as it should:
`public record Post
{
public string Body { get; }
public RecordsCollection
To see the code examples and some Unit Tests covering certain scenarios, just follow this link.
If you have some thoughts, suggestions, or spotted a mistake, please drop me a message.
I wish you a pleasant experience while writing a code with C# 9.0.
Take care, Ievgen
Frequently Asked Questions
What is the record type in C# 9.0?
The record type in C# 9.0 is a reference type designed for immutable objects, where the compiler automatically generates equality members. With a plain class you write Equals, GetHashCode, and comparison logic by hand; declaring the type as a record instead gives you value-based equality out of the box, so two records with the same property values are considered equal.
Why does value equality break for C# records that contain a collection?
A C# record's automatic value equality breaks when a property is a collection such as ReadOnlyCollection<T> or ImmutableList<T>, because those are reference types and are compared by reference, not by their elements. Two records holding collections with identical contents will therefore be treated as unequal, since the compiler-generated equality compares the collection references.
How do you make a C# record compare collection properties by value?
Wrap the collection in a custom type that overrides Equals and GetHashCode. For example, derive a RecordsCollection<T> from ReadOnlyCollection<T>, override Equals to return true when the other instance's Items.SequenceEqual(Items), and build GetHashCode from each element using System.HashCode. The record then compares those collection properties by element sequence rather than by reference.
What are the advantages and disadvantages of immutable objects in C#?
Immutable objects in C# eliminate unexpected side effects when sharing state, including across threads, and help maintain a consistent state. The trade-offs: in performance-critical systems they can cause higher data allocation, and they normally require extra code for equality comparers and for creating new instances when a property changes. C# 9.0 records reduce that extra code.