Problem
How to create an immutable type in C#.
Solution
Create a class with:
- Fields: that are
private
and readonly
.
- Constructors:
Public
: that accepts parameters to initialize the class
Private
: that accepts parameter for all the fields/properties of the class
- Properties: that are read-only.
- Methods: that return a new object (of 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)
{
}
private Email(string subject, string from, string body,
List<string> to, List<string> cc, List<string> bcc,
List<string> attachments)
{
}
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_orignal_unchanged()
{
var email = new Email(
subject: "Hello Immutable Type",
from: "james@bond.com",
body: "Immutable Types are good for value objects",
to: "joker@circus.com");
var newEmail = email.AddTo("riddler@puzzled.com");
Assert.Equal(1, email.To.Count);
Assert.Equal(2, newEmail.To.Count);
}
Discussion
The idea behind immutable types is that once initialized, they don’t mutate their state and instead create & return a new object.
By making the fields read-only
, we ensure that once initialized in a constructor, they can’t be modified, even by the code in the class itself.
Public
constructor that accepts initialization data is required so that client can pass-in minimum state for the type to be valid. Private
constructor, on the other hand, is used by methods to construct a new object and set all its state.
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 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., £5 note can’t become £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. E.g., 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 same fields/state.