When you need to prevent changes to an instance of an object most of the time, but not all of the time, the Immutable pattern (implemented by the ImmutableAttribute aspect) will be too aggressive for you. In these situations, you need a pattern that allows you to define the point in time where immutability begins. To accomplish this you can make use of the FreezableAttribute aspect.
Changes in an object with the ImmutableAttribute aspect will be forbidden as soon as you call the Freeze() method. Any further attempt to modify the object will result in an ObjectReadOnlyException. Any attempt to share the object before you call the Freeze() method will result in a ThreadMismatchException.
To make an object freezable, all you need to do is add the FreezableAttribute attribute to the class in question.
Making an object freezable
To make an object freezable:
Add the PostSharp.Patterns.Threading package to your project using NuGet.
Add the FreezableAttribute custom attribute to your class.
using PostSharp.Patterns.Threading; [Freezable] public class Invoice { public long Id { get; set; } }
Annotate your object model for parent/child relationships as described in Annotating an Object Model for Parent/Child Relationships (Aggregatable).
Freezing an object
To freeze an object, you will first have to cast the object to the IFreezable interface. After that, you will be able to call the Freeze() method.
var invoice = new Invoice();
invoice.Id = 123456;
((IFreezable)invoice).Freeze();
Note
The IFreezable interface will be injected into the Invoice
class after compilation. Tools that are not aware of PostSharp may incorrectly report that the Invoice
class does not implement the IFreezable interface.
Instead of using the cast operator, you can also use the Cast<TSource, TTarget>(TSource) method. This method is faster and safer than the cast operator because it is verified and compiled by PostSharp at build time.
Note
If you are attempting to freeze either AdvisableCollection<T> or AdvisableDictionary<TKey, TValue> you will not be able to use the cast operator or the Cast<TSource, TTarget>(TSource) method. Instead, you will have to use the QueryInterface<T>(object, bool) extension method.
Once you’ve called the Freeze() method on an object instance the code will no longer be able to change the property values on that instance. If a value change is attempted the code will throw an ObjectReadOnlyException.
var invoice = new Invoice();
invoice.Id = 123456;
((IFreezable)invoice).Freeze();
// This will throw an exception.
invoice.Id = 345678;
Rules enforced by the Freezable aspect
A freezable 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 Freeze() method has not yet been called.
ObjectReadOnlyException if a field or property is being modified after the Freeze() method has been called.
Determining whether an object is in frozen state
To determine whether an object has been frozen, cast it to IThreadAware and get the readonly value from IsReadOnly via the ConcurrencyController property.
var invoice = new Invoice();
invoice.Id = 123456;
((IFreezable)invoice).Freeze();
// The 'frozen' property will be set to 'true'.
bool frozen = ((IThreadAware)invoice).ConcurrencyController.IsReadOnly;
Freezable object trees
The Freezable pattern relies on the Aggregatable pattern. The AggregatableAttribute aspect will be implicitly added to the target class. Therefore, you can not only create freezable classes, but also freezable object trees. Read the Parent/Child, Visitor and Disposable for more information on how to establish object trees.
Important
Children of freezable objects must be either freezable or immutable. Therefore, children classes must be annotated with the FreezableAttribute or ImmutableAttribute custom attribute. Collection types must be derived from AdvisableCollection<T> or AdvisableDictionary<TKey, TValue>. Arrays cannot be used.
See Also
Reference
FreezableAttribute
AggregatableAttribute
ChildAttribute
ParentAttribute
ImmutableAttribute
AdvisableDictionary<TKey, TValue>
AdvisableCollection<T>
Other Resources
Immutable Threading Model
Parent/Child, Visitor and Disposable
Annotating an Object Model for Parent/Child Relationships (Aggregatable)
Working With Child Collections