Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Ensure Immutability of a Generic

This example is in C# but the question really applies to any OO language. I'd like to create a generic, immutable class which implements IReadOnlyList. Additionally, this class should have an underlying generic IList which is unable to be modified. Initially, the class was written as follows:

public class Datum<T> : IReadOnlyList<T>
{
    private IList<T> objects;
    public int Count 
    { 
        get; 
        private set;
    }
    public T this[int i]
    {
        get
        {
            return objects[i];
        }
        private set
        {
            this.objects[i] = value;
        }
    }

    public Datum(IList<T> obj)
    {
        this.objects = obj;
        this.Count = obj.Count;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    public IEnumerator<T> GetEnumerator()
    {
        return this.objects.GetEnumerator();
    }
}

However, this isn't immutable. As you can likely tell, changing the initial IList 'obj' changes Datum's 'objects'.

static void Main(string[] args)
{
    List<object> list = new List<object>();
    list.Add("one");
    Datum<object> datum = new Datum<object>(list);
    list[0] = "two";
    Console.WriteLine(datum[0]);
}

This writes "two" to the console. As the point of Datum is immutability, that's not okay. In order to resolve this, I've rewritten the constructor of Datum:

public Datum(IList<T> obj)
{
    this.objects = new List<T>();
    foreach(T t in obj)
    {
        this.objects.Add(t);
    }
    this.Count = obj.Count;
}

Given the same test as before, "one" appears on the console. Great. But, what if Datum contains a collection of non-immutable collection and one of the non-immutable collections is modified?

static void Main(string[] args)
{
    List<object> list = new List<object>();
    List<List<object>> containingList = new List<List<object>>();
    list.Add("one");
    containingList.Add(list);
    Datum<List<object>> d = new Datum<List<object>>(containingList);
    list[0] = "two";
    Console.WriteLine(d[0][0]);
}

And, as expected, "two" is printed out on the console. So, my question is, how do I make this class truly immutable?

like image 351
cscan Avatar asked Dec 05 '14 18:12

cscan


People also ask

How do you enforce immutability?

On value types (which usually should be immutable from a design perspective), immutability can be enforced by applying the readonly modifier on the type. It will fail to compile if it contains any members that are not read-only.

How do you break immutability?

So, even though, the field which is pointing to Date or Collection or array object is final, you can still break the immutability of the class by breaking Encapsulation by returning a reference to the original mutable object.

What is the immutability of an object?

An object is considered immutable if its state cannot change after it is constructed. Maximum reliance on immutable objects is widely accepted as a sound strategy for creating simple, reliable code. Immutable objects are particularly useful in concurrent applications.

What is the use of immutability?

One of the advantages of immutability is that you can optimize your application by making use of reference and value equality. This makes it easy to identify if anything has changed. You can consider the example of state change in the React component.


1 Answers

You can't. Or rather, you don't want to, because the ways of doing it are so bad. Here are a few:

1. struct-only

Add where T : struct to your Datum<T> class. structs are usually immutable, but if it contains mutable class instances, it can still be modified (thanks Servy). The major downside is that all classes are out, even immutable ones like string and any immutable class you make.

var e = new ExtraEvilStruct();
e.Mutable = new Mutable { MyVal = 1 };
Datum<ExtraEvilStruct> datum = new Datum<ExtraEvilStruct>(new[] { e });
e.Mutable.MyVal = 2;
Console.WriteLine(datum[0].Mutable.MyVal); // 2

2. Create an interface

Create a marker interface and implement it on any immutable types you create. The major downside is that all built-in types are out. And you don't really know if classes implementing this are truly immutable.

public interface IImmutable
{
    // this space intentionally left blank, except for this comment
}
public class Datum<T> : IReadOnlyList<T> where T : IImmutable

3. Serialize!

If you serialize and deserialize the objects that you are passed (e.g. with Json.NET), you can create completely-separate copies of them. Upside: works with many built-in and custom types you might want to put here. Downside: requires extra time and memory to create the read-only list, and requires that your objects are serializable without losing anything important. Expect any links to objects outside of your list to be destroyed.

public Datum(IList<T> obj)
{
    this.objects =
      JsonConvert.DeserializeObject<IList<T>>(JsonConvert.SerializeObject(obj));
    this.Count = obj.Count;
}

I would suggest that you simply document Datum<T> to say that the class should only be used to store immutable types. This sort of unenforced implicit requirement exists in other types (e.g. Dictionary expects that TKey implements GetHashCode and Equals in the expected way, including immutability), because it's too difficult for it to not be that way.

like image 65
Tim S. Avatar answered Nov 03 '22 12:11

Tim S.