There are times when you want certain objects in your codebase to retain their post creation state without the possibility of it ever changing. These objects are said to be immutable. Immutable objects are useful in multithreaded applications because they can be safely accessed by several threads concurrently, without the need for locking or other synchronization. PostSharp offers the ImmutableAttribute aspect that allows you enforce this pattern on your objects.
Changes in an object with the ImmutableAttribute aspect will be forbidden as soon as the object constructor exits. Any further attempt to modify the object will result in an ObjectReadOnlyException.
Note
The Immutable pattern can be too strong for some common object-oriented scenarios, for instance with serializable classes. In some cases, the Freezable object is a better choice. For details, see Freezable Threading Model.
Making a class immutable
To add the Immutable pattern manually:
Add a reference to the
PostSharp.Patterns.Threading
package to your project.Add the ImmutableAttribute custom attribute to your class.
Annotate your object model for parent/child relationships as described in Annotating an Object Model for Parent/Child Relationships (Aggregatable).
Rules enforced by the Immutable aspect
An immutable object will throw the following exceptions at run-time:
ThreadMismatchException if both following conditions are simultaneously true:
you access the object from a different thread than the one that created it, and
the constructor has not completed yet.
ObjectReadOnlyException if a field or property is being modified after the constructor has completed.
Immutable object trees
Because the Immutable pattern is an implementation of the Aggregatable pattern, all of the same behaviors of the AggregatableAttribute are available. As a result, you can create both immutable classes and immutable object trees. For more information regarding object trees, read Parent/Child, Visitor and Disposable.
Note
Children of immutable objects must be marked as Immutable or Freezable themselves. Adding ImmutableAttribute or FreezableAttribute to the child classes will accomplish this. Freezable children will be automatically frozen when the constructor of the parent completes.
Immutable vs readonly
Many C# developers make use of the readonly
keyword in an attempt to make their objects immutable. The readonly
keyword doesn't guarantee immutability though. Using readonly
only ensures that no method other than the object's constructor can alter the variable's value. It doesn't, however, prevent you from altering values on complex objects outside of the constructor.
The readonly keyword may be too strict
In the following code sample, the _id
variable is a primitive type and can't be altered outside the constructor. This is enforced at compile time and an error would be displayed where the SetIdentifier
method attempts to change the _id
field value. The compiler does not see that the SetIdentifier
method is called only from the constructor. In this example, the readonly
keyword is too strong even if the class is legitimately immutable.
public class Invoice
{
public readonly long _id;
public Invoice(long id)
{
SetIdentifier(id);
}
private void SetIdentifier(long id)
{
// Will cause compilation error.
_id = id;
}
}
The readonly keyword may also be too loose
When you have complex entities composed of several objects, immutability is a characteristic of the whole entity, not of a single object. However, this does not fit with the readonly
keyword.
In the following example, the Invoice
is not immutable even if the _invoiceHeader
field is readonly
.
public class Invoice
{
public readonly InvoiceHeader _invoiceHeader;
public Invoice()
{
_invoiceHeader = new InvoiceHeader();
}
public void Refresh()
{
// Valid but not immutable.
_invoiceHeader.CustomerName = "Jim";
_invoiceHeader.CustomerPhone = "555-123-9876";
}
}
The same type of change to object state can happen with collections. You can not reinitialize a readonly
collection, but you can freely Add
, Remove
, Clear
and do other operations that the collection itself exposes. Additionally, if the collection contains complex types you are able to change values on each instance that the collection contains.
public class Invoice
{
public readonly IList<Item> _items;
public Invoice()
{
_items = new List<Item>();
}
public void Refresh()
{
//will cause a compilation error
_items = new List<Item>();
//valid but not immutable
_items.Add(new Item());
_items[0].Price = 3.50;
_items.RemoveAt(0);
}
}
As you can see there is no way to use the readonly
keyword to make complex object graphs immutable. Combining the ImmutableAttribute, ChildAttribute, AdvisableCollection<T> and the AdvisableDictionary<TKey, TValue> types allows you to make immutable objects that guarantee no changes to primitive or complex objects after constructor execution has completed.
Constructor execution
Objects are not frozen until the last constructor has finished executing. Because of this, you can use the constructor to set up the state of the parent instance through its own constructor as well as chained, or inherited object, constructors. You're also able to make changes to child object instances through their constructors at this time.
[Immutable]
public class Invoice : Document
{
public Invoice(long id) : base(id)
{
Items = new AdvisableCollection<Item>();
Items.Add(new Item("widget"));
}
[Child]
public AdvisableCollection<Item> Items { get; set; }
}
[Immutable]
public class Document
{
private long _id;
public Document (long id)
{
_id = id;
}
}
[Immutable]
public class Item
{
public Item (string name)
{
Name = name;
}
public string Name { get; set; }
}
In this example, the constructors finish executing in the order of Document
, Item
and finally Invoice
. It is not until after the Invoice
constructor finishes executing that the object graph is made immutable.
Immutable collections
When authoring immutable object models, immutable collections are a good replacement for advisable collections. Immutable collections are implemented in the System.Collections.Immutable
namespace, contained in the System.Collections.Immutable
NuGet package.
See Also
Reference
ImmutableAttribute
AggregatableAttribute
AdvisableDictionary<TKey, TValue>
AdvisableCollection<T>
Other Resources