Reputation: 21794
I find myself having to create a lot of immutable classes and I'd like to find a way to do it with no redundant information. I can't use an anonymous type because I need to return these classes from methods. I want intellisense support, so I'd prefer not to use Dictionaries, dynamic or anything like that. I also want well-named properties, which rules out Tuple<>. So far, some patterns I've tried:
// inherit Tuple<>. This has the added benefit of giving you Equals() and GetHashCode()
public class MyImmutable : Tuple<int, string, bool> {
public MyImmutable(int field1, string field2, bool field3) : base(field1, field2, field3) { }
public int Field1 { get { return this.Item1; } }
public string Field2 { get { return this.Item2; } }
public bool Field3 { get { return this.Item3; } }
}
///////////////////////////////////////////////////////////////////////////////////
// using a custom SetOnce<T> struct that throws an error if set twice or if read before being set
// the nice thing about this approach is that you can skip writing a constructor and
// use object initializer syntax.
public class MyImmutable {
private SetOnce<int> _field1;
private SetOnce<string> _field2;
private SetOnce<bool> _field3;
public int Field1 { get { return this._field1.Value; } set { this._field1.Value = value; }
public string Field2 { get { return this._field2.Value; } set { this._field2.Value = value; }
public bool Field3 { get { return this._field3.Value; } set { this._field3.Value = value; }
}
///////////////////////////////////////////////////////////////////////////////////
// EDIT: another idea I thought of: create an Immutable<T> type which allows you to
// easily expose types with simple get/set properties as immutable
public class Immutable<T> {
private readonly Dictionary<PropertyInfo, object> _values;
public Immutable(T obj) {
// if we are worried about the performance of this reflection, we could always statically cache
// the getters as compiled delegates
this._values = typeof(T).GetProperties()
.Where(pi => pi.CanRead)
// Utils.MemberComparer is a static IEqualityComparer that correctly compares
// members so that ReflectedType is ignored
.ToDictionary(pi => pi, pi => pi.GetValue(obj, null), Utils.MemberComparer);
}
public TProperty Get<TProperty>(Expression<Func<T, TProperty>> propertyAccessor) {
var prop = (PropertyInfo)((MemberExpression)propertyAccessor.Body).Member;
return (TProperty)this._values[prop];
}
}
// usage
public class Mutable { int A { get; set; } }
// we could easily write a ToImmutable extension that would give us type inference
var immutable = new Immutable<Mutable>(new Mutable { A = 5 });
var a = immutable.Get(m => m.A);
// obviously, this is less performant than the other suggestions and somewhat clumsier to use.
// However, it does make declaring the immutable type quite concise, and has the advantage that we can make
// any mutable type immutable
///////////////////////////////////////////////////////////////////////////////////
// EDIT: Phil Patterson and others mentioned the following pattern
// this seems to be roughly the same # characters as with Tuple<>, but results in many
// more lines and doesn't give you free Equals() and GetHashCode()
public class MyImmutable
{
public MyImmutable(int field1, string field2, bool field3)
{
Field1 = field1;
Field2 = field2;
Field3 = field3;
}
public int Field1 { get; private set; }
public string Field2 { get; private set; }
public bool Field3 { get; private set; }
}
These are both somewhat less verbose than the "standard" pattern of creating readonly fields, setting them via a constructor, and exposing them via properties. However, both of these methods still have lots of redundant boilerplate.
Any ideas?
Upvotes: 8
Views: 396
Reputation: 81337
Immutable classes are most useful when there exists a corresponding mutable class which whose contents can easily be loaded from an immutable-type instance or copied to a new immutable-type instance. If one has an immutable object needs to make more than 2-3 "changes" to it (i.e. change the reference so it points to an immutable which is suitably different from the original), copying the data to a mutable object, changing it, and storing it back is often more practical than creating an object which differs from the original in one way, then creating a new object which differs from that in another way, etc.
A nice easily-generalized approach is to define a (possibly internal) exposed-field structure type, and then have both the mutable and immutable classes hold a field of that type. The accessor properties would have to be defined separately for both class types, but one could have both types' GetHashCode
and Equals
methods chain to the corresponding methods of the underlying struct. Creating an instance of either type given an instance of the other could be accomplished using a single struct assignment, rather than having to copy all of the members individually.
If one's programmers understand how structures work (I'd consider such knowledge fundamental, even if others disagree) one could make the structure type public. Having a mutable holder class may be useful even if the structure is public, since there are times when reference semantics are useful (e.g. one may want to be able to change something stored in a Dictionary
without having to change the Dictionary
itself), but the convert-to-mutable/change/convert-to-immutable pattern works even better with structure types than with class types.
Upvotes: 1
Reputation: 100630
See if public {get;private set;}
properties work for your case - a bit more compact than separate field declaration, exactly the same semantic.
Update: As ChaseMedallion commented and inlined in the question this approach does not provide automatically generated GetHashCode
and Equals
methods unlike Tuple
approach.
class MyImmutable
{
public int MyProperty {get; private set;}
public MyImmutable(int myProperty)
{
MyProperty = v;
}
}
I like Tuple
approach as it gives object that can safely used in interesting contexts and provides nice names. If I would need to create many types of such kind I would consider to re-implement Tuple
classes:
GetHashCode
and store as part of object to avoid unbounded checks for collections/strings. Possibly optional to allow opt in for cases that are commonly used as keys in Dictionary
. protected
or just hide from intellisence with EditorBrowsableAttribute) so there is no confusion about 2 set of names. Side note: check out Eric Lippert's series on immutable types.
Upvotes: 1
Reputation: 1262
You could use automatic properties with private setters
public class MyImmutable
{
public MyImmutable(int field1, string field2, bool field3)
{
Field1 = field1;
Field2 = field2;
Field3 = field3;
}
public int Field1 { get; private set; }
public string Field2 { get; private set; }
public bool Field3 { get; private set; }
}
Upvotes: 4