5

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?

cscan
  • 3,684
  • 9
  • 45
  • 83
  • 2
    There is no constraint you could use to do so. – MarcinJuraszek Dec 05 '14 at 18:43
  • 3
    That's the logical consequence of having an immutable collection of mutable type. Given a `class S { public string s; }`, and a `Datum d`, there's nothing preventing modification of `d[0].s`, either. There's no way to prevent that. –  Dec 05 '14 at 18:46
  • As pointed out by [brz](https://stackoverflow.com/users/3246354) before they deleted their answer, the package for [Immutable Collections](https://www.nuget.org/packages/System.Collections.Immutable) would probably interest anyone implementing code like this. Documentation [here](https://msdn.microsoft.com/en-us/library/system.collections.immutable.aspx). – jpmc26 Jul 12 '17 at 00:45

5 Answers5

6

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.

Community
  • 1
  • 1
Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • 1
    Note that even if you use a struct, and wouldn't be able to mutate the literal bits representing the struct, you could have a struct with some form of logical reference to mutable state, allowing the caller to mutate the (conceptual) values that represent the struct *that is actually in the list*. As for the interface; it's quite easy to lie and have a mutable class implement an interface with only read semantics. `IReadOnlyList` is a great example of this, as it's implemented by plenty of mutable objects. – Servy Dec 05 '14 at 19:05
  • 2
    Basically, in short, you can ask politely that immutable objects be used, but it is *impossible* to actually prevent a determined user from passing in mutable state to the type. – Servy Dec 05 '14 at 19:09
  • And note that the struct could even be immutable and still hold a reference to a mutable reference type. This makes the struct's bits immutable, but the logical object that the struct represents is mutable. – Servy Dec 05 '14 at 19:11
  • @Servy Oi, good point. Immutability is tricky, and we're just scratching the surface. – Tim S. Dec 05 '14 at 19:12
  • @Servy Now that you bring up the problem with structs I begin to wonder if true immutability is even possible in languages that have immutability as a language feature - like D. What are your thoughts? – cscan Dec 05 '14 at 19:21
  • @cscan For someone with admin rights on the machine in question, they can always subvert the code and just write directly to the machine's memory, mutating the values of an "immutable" type, regardless of that language's restrictions. You'd need to be very specific in how you framed the question to get a meaningful answer. For example, are you asking if code written *from the same program, written in the same language* could could violate the immutability constraint? But yes, many people may be surprised by the ability of some of these seemingly valid constraints to be violated in odd cases. – Servy Dec 05 '14 at 19:24
  • @Servy Ah, sorry, yes, I realize this is a very nuanced topic. The question applies to a library and whether or not the immutability of one of the classes in said library can be guaranteed. This assumes that one is using this library within the same program and language and disregards the possibility of directly writing to memory. – cscan Dec 05 '14 at 19:32
  • @cscan That still leaves you with needing to look at the specific language, and the specific means of defining it as being "immutable". My guess is that more than you'd expect will turn out to be able to be violated, if not all of them. Of course, I couldn't speak to every single possible situation, or even most, for that matter. I can say for sure that it's impossible in C# though. – Servy Dec 05 '14 at 19:46
0

Kind of hacky, and definitely more confusing than it's worth in my opinion, but if your T is guaranteed to be serializable, you can store string representations of the objects in your collection rather than storing the objects themselves. Then even if someone pulls an item from your collection and modifies it, your collection would still be intact.

It would be slow and you'd get a different object every time you pulled it from the list. So I'm not recommending this.

Something like:

public class Datum<T> : IReadOnlyList<T>
{
    private IList<string> objects;
    public T this[int i] {
        get { return JsonConvert.DeserializeObject<T>(objects[i]); }
        private set { this.objects[i] = JsonConvert.SerializeObject(value); }
    }

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

    public IEnumerator<T> GetEnumerator() {
        return this.objects.Select(JsonConvert.DeserializeObject<T>).GetEnumerator();
    }
}
Joe Enos
  • 39,478
  • 11
  • 80
  • 136
  • This doesn't prevent someone from using a mutable type as the items in the list, and mutating the values that they get out of the collection. Heck, it's not even impossible to create a type who's value is dependent on some mutable state that would be persisted across serialization (for example, an object that represents data in a file, registry key, web service call, or database query). – Servy Dec 05 '14 at 18:57
  • Yep, plenty of gotchas there. But since each object is serialized only once into a string during the collection's constructor, you're guaranteed that your collection never changes. The collection might become stale, and you can edit the objects you pull out of the collection, but your collection itself is a snapshot and unchangeable. So it might potentially be a solution to a specific need. – Joe Enos Dec 05 '14 at 19:03
0

It's impossible. There's no possible way to constrain the generic type to be immutable. The best that you can possibly do is write a collection that cannot allow the structure of that collection to be modified. There is no way to prevent the collection from being used as a collection of some mutable type.

Servy
  • 202,030
  • 26
  • 332
  • 449
0

think that such collections are not match OOP, because this design leads to specific co-relation between independent classes - collection and it's items. How one class can change behavior of other without knowlege of each other?

So suggestions of serialization and so can allow you to do it on hacky way, but better is to decide if it's so required to make collection of immutable items, who trys to change them except your own code? May be better "to not mutate" items rather than try "make them immutable".

comdiv
  • 865
  • 7
  • 26
0

I faced the same problem, where I implement an object (say CachedData<T>) which handles a cached copy of the property of another object (say T SourceData). When calling the constructor of CachedData, you pass a delegate which returns a SourceData. When calling CachedData<T>.value, you get a copy of SourceData, which is updated every now and then.

It would make no sense to try caching an object, as .Value would only cache the reference to the data, not the data itself. It would only make sense to cache data types, strings, and perhaps structures.

So I ended up:

  1. Thoroughly documenting CachedData<T>, and
  2. Throwing an error in the constructor if T is neither a ValueType, a Structure, or a String. Some like (forgive my VB): If GetType(T) <> GetType(String) AndAlso GetType(T).IsClass Then Throw New ArgumentException("Explain")
Ama
  • 1,373
  • 10
  • 24