Reputation: 2720
I was asked in a CMMI level 5 company interview about how to create a mutable and immutable class in C#. I have heard of mutable and immutable which means can change and cannot change, like String and StringBuilder.
But, I was not aware of how to create a mutable class and immutable class. We have String and String builder for this. But when it comes to creating one, I was forced to search the web for this, but couldn't find anything useful, so thought of asking here.
However, I tried creating it by defining a property in the class and on its Getter, I created a new object of string to replicate it. But was not successful in understanding.
Also, I have referred that a question is already asked in stackoverflow about immutable and mutable. But, my question is different. I wanted to know if I wanted to create a mutable class then how will I go with it apart from using String or other mutable classes.
Upvotes: 3
Views: 5690
Reputation: 155698
As of late 2020, C# 9.0 for .NET 5.0 has been released which supports immutable record types (with support for copy-constructors, and with the with
operator to easily create new instances with new property values).
C# does not have the same level of support for const
-correctness that C++ offers (ignoring Code Contracts), but it does still provide for the readonly
modifier (and true read-only auto-properties in C# 6.0) which helps.
C# also lacks syntactic support for Record types, which unfortunately were pulled from C# 7, so we'll have to wait another year for that (Update: As of mid-2018, C# 8.0 is expected to have Record types, but C# 8.0 probably won't be finally released until into 2020 given its very long list of new features).
Anyway, an immutable type in .NET is just a POCO1 which cannot have its state modified after construction. Note that this is only enforced by the compiler if your type has every field tagged as readonly
and that every complex (i.e. non-scalar) member is also similarly constrained.
If your type has any array members then the type cannot be truly immutable because there is no enforcement of read-only buffers in C# (C++ does). Which means in-practice that an "immutable" type in C# is just a well-designed POCO that a consumer (who would be playing by the rules (e.g. no reflection)) can make certain assumptions about when using it, for example, an immutable type is inherently thread-safe. But that's it. There are no special AOT or JIT optimisations nor any special behaviour exhibited by the runtime. It's a very "human factors"-kinda thing.
This class below is immutable:
class Immutable {
private readonly String foo;
public Immutable(String foo, String bar) {
this.foo = foo;
this.Bar = bar;
}
public String Bar { get: }
public String Baz { get { return this.foo.Substring( 0, 2 ); } }
}
It is immutable because every field (i.e. its instance-state) is both readonly
and immutable (we only know this because System.String
is well known to be immutable). If foo
were changed to StringBuilder
or XmlElement
then it would no longer be immutable.
Note that strictly speaking, the readonly
modifier is not necessary for immutability, it just makes it easier to demonstrate, and it does add some level of compile-time enforcement (and possibly some runtime optimisation).
For comparison's sake, this class is not immutable (i.e. it is mutable):
class Mutable {
private readonly Int32[] values;
public Mutable(Int32 values) {
this.values = values;
}
public Int32[] GetValues() {
return this.values;
}
}
It is mutable because:
Int32[]
(an array type) is mutableGetValues
Here's an example demonstrating why it's not immutable:
Int32[] values = { 0, 1, 2, 3 };
Mutable mutable = new Mutable( values );
Print( mutable.GetValues() ); // prints "0, 1, 2, 3"
values[0] = 5;
Print( mutable.GetValues() ); // prints "5, 1, 2, 3"
If Mutable
were immutable then subsequent changes to values
would not be visible when using Mutable
's API: the second call to Print
would display the same output as the first.
However, you can have an immutable type even if you are using arrays or complex types: and this is done by that hiding all the ways to modify state. For example, return ReadOnlyCollection<Int32>
instead of Int32[]
and always perform a deep copy/clone of all complex and mutable values passed-in. But the compiler, JIT, and runtime are still not sophisticated enough to determine this renders the object type immutable - hence why you have to document it and trust your consumer to use it properly (or if you're a consumer, trust your upstream dev that they implemented it correctly)
Here's an example of an immutable type that contains an array:
class Immutable {
private readonly Int32[] values;
public Mutable(Int32 values) {
if( values == null ) throw new ArgumentNullException(nameof(values));
this.values = (Int32[])values.Clone();
}
public IReadOnlyList<Int32> GetValues() {
return this.values;
}
}
Array.Clone()
) during construction - so any future changes to the object passed into the constructor won't affect any Immutable
class instances.
values
array contained non-immutable, non-scalar values then the constructor would have to perform a "deep-copy" of the elements to ensure its values would be isolated from any future changes elsewhere.values
array is never directly exposed to consumers.GetValues()
returns an IReadOnlyList<T>
view of the internal array (this is new in .NET 4.5). This is more lightweight than returning a ReadOnlyCollection<T>
wrapper (introduced in .NET 2.0).1: A POCO is a "Plain Old CLR Object" type, which in-practice means any class
or struct
without any requirements that it inherit some parent supertype or implement any particular interface. The term is often used in reference to ORM libraries like Linq-to-SQL, Entity Framework or NHibernate which (in their earlier versions) required each entity class to derive from some base entity type, or employ certain techniques (e.g. INotifyPropertyChanged
). See here for more details: What is POCO in Entity Framework?
Upvotes: 7