Immutability is still an area where C# is maturing. So far, C# has not adopted the const
semantics of C++ ... which I actually think is a good thing. The behavior of const
in C++ often made it challenging to design class hierarchies that worked they way you wanted. It wasn't uncommon to see code peppered with const_cast<>
to bypass constness where it wasn't desirable. Hopefully, the designers of C# will invent a simpler but still expressive alternative.
At present, there is no language feature that marks parameters or objects passed to methods as immutable. The best you can do is pass an an object using an interface (or better yet a wrapper) that only permits read operations.
For some of the standard collection in .NET you can use the ReadOnlyCollection
wrapper, which encapsulated any mutable ICollection
type within a read-only container.
Creating immutable types requires planning and awareness of the language features. For example, the readonly
keyword is your friend. It allows you to declare members of a class or struct as immutable. Unfortunately, this immutability only applies to the reference, not the members of the referenced object. What this means is that you can declare:
private readonly int[] m_Values = new int[100];
public void SomeMethod()
{
m_Values = new int[50]; // illegal, won't compile!
m_Values[10] = 42; // perfectly legal, yet undesirable
}
In the above example, the reference to the array is immutable, but the individual elements of the array are not. This behavior extends beyond arrays, of course.
A practice I've found helpful when designing immutable types, is to separate the immutable behavior into its own interface which is then implemented by a class that manages the data. The interface only exposes get properties and methods that are guaranteed not to mutate the state of the object. It is then possible to pass instances of your type to methods as parameters of that interface type. This is a weak form of immutability support - since the called method can often cast the reference to the mutable type. A better, but more cumbersome alternative, is to create a wrapper implementation that implements that same interface and maintains a reference to the actual instance (much like ReadOnlyCollection
does). This is more work but provides a stronger guarantee for immutability.
The approach you choose to use depends on how important guarantees of immutability are, and how much time and effort you are willing to spend to get there.
If you're interested in reading more on this topic, Eric Lippert has an excellent series of articles on immutability in C#.