Problem
How to create an immutable type in C#.
Solution
Create a class with.
- Fields: that are private and read-only.
- Constructors
- Public: that accepts parameters to initialize the class
- Private: that accepts parameters for all the fields/properties of the class
- Properties: that are read-only.
- Methods: that return a new object (of the same type) instead of mutating the state (fields/properties).
public sealed class Email
{
private readonly List<string> to;
private readonly List<string> cc;
private readonly List<string> bcc;
private readonly List<string> attachments;
public Email(string subject, string from, string body, string to)
{
// set fields values
}
private Email(string subject, string from, string body,
List<string> to, List<string> cc, List<string> bcc,
List<string> attachments)
{
// set fields values
}
public string Subject { get; }
public string From { get; }
public string Body { get; }
public IReadOnlyList<string> To => to;
public IReadOnlyList<string> CC => cc;
public IReadOnlyList<string> BCC => bcc;
public IReadOnlyList<string> Attachments => attachments;
public Email AddTo(string to)
{
return new Email(
subject: this.Subject,
from: this.From,
to: new List<string>().Concat(this.to).Append(to).ToList(),
body: this.Body,
cc: this.cc,
bcc: this.bcc,
attachments: this.attachments);
}
}
The test below shows that the old instance remains unchanged.
[Fact]
public void Changing_email_returns_new_email_and_leave_original_unchanged()
{
var email = new Email(
subject: "Hello Immutable Type",
from: "[email protected]",
body: "Immutable Types are good for value objects",
to: "[email protected]"
);
var newEmail = email.AddTo("[email protected]");
Assert.Equal(1, email.To.Count);
Assert.Equal(2, newEmail.To.Count);
}
Discussion
The idea behind the immutable types is that once initialized, they don’t mutate their state but instead, create & return a new object.
By making the fields read-only, we ensure that once initialized in a constructor, these can’t be modified, even by the code in the class itself.
For the public constructor that accepts initialization, data is required so that the client can pass in a minimum state for the type to be valid. A private constructor, on the other hand, is used by methods to construct a new object and set all its states.
Properties must be made read-only by using get-only properties or expression-bodied functions. Note also the use of IReadOnlyList; this is to ensure that clients can’t add to lists. Methods will construct a new object based on the current state.
Usage
Of course, creating immutable types is extra work, so why and when should we use them?
I’ve found that Value Objects are good candidates for this. These are abstractions based on their values and therefore it doesn’t make sense for one object to “mutate” into another; e.g., a £5 note can’t become a £10 note, each note (object) is defined by its value and is distinct.
I’ve also found them useful in writing library code that other developers use. For example, if you’re building a library to connect to Azure NoSQL and have a class AzureNoSqlSettings that is being passed to this library, it is useful to make this settings class immutable so that it is clear to all developers (working on library) that settings object must not change once set by the client.
Immutable Types are also useful in concurrency scenarios since multiple threads are not mutating the same fields/state.
Source Code
GitHub