17

Ok, as I understand it, immutable types are inherently thread safe or so I've read in various places and I think I understand why it is so. If the inner state of an instance can not be modified once the object is created there seems to be no problems with concurrent access to the instance itself.

Therefore, I could create the following List:

class ImmutableList<T>: IEnumerable<T>
{
    readonly List<T> innerList;

    public ImmutableList(IEnumerable<T> collection)
    {
         this.innerList = new List<T>(collection);
    }

    public ImmutableList()
    {
         this.innerList = new List<T>();
    }

    public ImmutableList<T> Add(T item)
    {
         var list = new ImmutableList<T>(this.innerList);
         list.innerList.Add(item);
         return list;
    }

    public ImmutableList<T> Remove(T item)
    {
         var list = new ImmutableList<T>(this.innerList);
         list.innerList.Remove(item);
         return list;
    } //and so on with relevant List methods...

    public T this[int index]
    {
        get
        {
            return this.innerList[index];
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return innerList.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return ((System.Collections.IEnumerable)this.innerList).GetEnumerator();
    }
}

So the question is: Is this really an immutable type? Is it really thread safe?

Obviously the type itself is immutable but there is absolutely no garantee that T is and therefore you could have concurrent access and threading issues related directly with the generic type. Would that mean that ImmutableList should be considered mutable?.

Should class ImmutableList<T>: IEnumerable<T> where T: struct be the only type truly considered immutable?

Thanks for any input on this issue.

UPDATE: A lot of answers/comments are concentrating on the particular implementation of ImmutableList I've posted which is probably not a very good example. But the issue of the question is not the implementation. The question I'm asking is if ImmutableList<MutableT> is really an immutable type considering everything that an immutable type entails.

InBetween
  • 32,319
  • 3
  • 50
  • 90
  • See the blogs from Eric Lippert regarding immutability: http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/ – Oded Jan 25 '12 at 20:17
  • 4
    Structs can be mutable, and objects can be immutable. – Sean U Jan 25 '12 at 20:20
  • 2
    you have infinite recursion in your `Remove` and `Add` methods currently ;-) – BrokenGlass Jan 25 '12 at 20:21
  • You could consider the list as a data structure 'immutable'. If this is not immutable, then any structure (list, tree, graph, etc.) which aggregates other elements can never be considered immutable. Unless of course you force the elements (type `T`) to implement some `Immutable` interface. – The Nail Jan 25 '12 at 20:22
  • 1
    I just figured I'd mention that there are better ways to make a collection thread safe than to make it immutable. Coping the entirety of the collection every time you would like to alter its contents will kill your performance/memory unless they are always very small. There are also lots of thread safe implementations of many data structures out there (or in the language specs). – Servy Jan 25 '12 at 20:29
  • 1
    @SeanU: The fact that structs can be mutable is irrelevant; in the sketch shown here no *variable* of type T is exposed to the caller, so there is no *variable* of struct type they can mutate. The relevant point is that *structs may contain references to mutable state*. – Eric Lippert Jan 25 '12 at 20:32
  • @BrokenGlass: Fixed, thanks. Didn't actually check the code for bugs :p – InBetween Jan 25 '12 at 21:01
  • @EricLippert Consider a struct containing a private array, and an indexer property that's used to get and set elements within the array. – Sean U Jan 25 '12 at 21:03
  • @SeanU: Right, that's what I said. Structs may contain references to mutable state, like a private array. – Eric Lippert Jan 25 '12 at 21:04
  • @EricLippert Six of one, half a dozen of the other. Behaviorally, `myStruct2 = myStruct1; myStruct2[0] = 100;` still sets `myStruct1[0]` to 100 just the same. And the consequences impact how you have to treat it in a collection like this one are the same as for mutable reference types. – Sean U Jan 25 '12 at 21:10
  • @Servy practical implementations of immutable data structures are designed to avoid copying the entire collection. For example, a list is defined as an item (the head) plus a reference to another list (the tail). When you add an item to the list, you just use the old list as the tail of the new one. No copying! – phoog Jan 26 '12 at 03:43
  • 3
    An immutable collection of `T` is one which will always hold the same instances of `T` as it starts with. If I have an `ImmutableList` with five instances of `Car` stored therein, it will always, as long as it exists, hold those same five instances of `Car`. Someone might paint the cars, or steal the hubcaps, but they'd still be the same five cars. – supercat Jan 27 '12 at 18:56
  • @EricLippert: The semantics you describe are one of the reasons I like mutable POD structs. If one has an `ImmutableList` one can know--merely by observing that `somePodType` is a POD with POD fields, that it's deeply immutable, without the inconveniences which might be imposed by making `somePodType` itself immutable; further, it's a lot easier to ensure visually that a struct is a POD than to ensure that an immutable struct doesn't access mutable state. – supercat Jan 27 '12 at 19:00
  • possible duplicate of [how-deep-would-you-expect-the-immutability-of-an-immutable-list-to-be](http://stackoverflow.com/questions/733412/how-deep-would-you-expect-the-immutability-of-an-immutable-list-to-be) – nawfal Jul 16 '14 at 09:40

6 Answers6

28

If the inner state of an instance can not be modified once the object is created there seems to be no problems with concurrent access to the instance itself.

That is generally the case, yes.

Is this really an immutable type?

To briefly sum up: you have a copy-on-write wrapper around a mutable list. Adding a new member to an immutable list does not mutate the list; instead it makes a copy of the underlying mutable list, adds to the copy, and returns a wrapper around the copy.

Provided that the underlying list object you are wrapping does not mutate its internal state when it is read from, you have met your original definition of "immutable", so, yes.

I note that this is not a very efficient way to implement an immutable list. You'd likely do better with an immutable balanced binary tree, for example. Your sketch is O(n) in both time and memory every time you make a new list; you can improve that to O(log n) without too much difficulty.

Is it really thread safe?

Provided that the underlying mutable list is threadsafe for multiple readers, yes.

This might be of interest to you:

http://blogs.msdn.com/b/ericlippert/archive/2011/05/23/read-only-and-threadsafe-are-different.aspx

Obviously the type itself is immutable but there is absolutely no garantee that T is and therefore you could have concurrent access and threading issues related directly with the generic type. Would that mean that ImmutableList<T> should be considered mutable?.

That's a philosophical question, not a technical one. If you have an immutable list of people's names, and the list never changes, but one of the people dies, was the list of names "mutable"? I would think not.

A list is immutable if any question about the list always has the same answer. In our list of people's names, "how many names are on the list?" is a question about the list. "How many of those people are alive?" is not a question about the list, it is a question about the people referred to by the list. The answer to that question changes over time; the answer to the first question does not.

Should class ImmutableList<T>: IEnumerable<T> where T: struct be the only type truely considered immutable?

I'm not following you. How does restricting T to be a struct change anything? OK, T is restricted to struct. I make an immutable struct:

struct S
{
    public int[] MutableArray { get; private set; }
    ...
}

And now I make an ImmutableList<S>. What stops me from modifying the mutable array stored in instances of S? Just because the list is immutable and the struct is immutable doesn't make the array immutable.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    [unless you would consider a name a proper pointer to a person:] The analogy with the dying people is a bit odd; one's name does not change in this event. A better analogy would be to make a list of people, not of names. – The Nail Jan 25 '12 at 20:37
  • 1
    @TheNail: The point is that the names are *references* to people. Though a problem with the analogy is of course, a person may change their name without changing their identity. – Eric Lippert Jan 25 '12 at 20:39
  • @TheNail names are things we use to *refer* to people. Of course, there can be two people with the same name (try writing about religion when there are two people with the same full name who write about other religions!), but we use names in a context where the name successfully refers to one person. It's a pretty close analogy to the way references work in C#, with the advantage of not using the words "address" or "pointer". – Jon Hanna Jan 25 '12 at 20:42
  • Furthermore a nice elaboration, with this as core sentence: 'A list is immutable if any question about the list always has the same answer'. – The Nail Jan 25 '12 at 20:44
  • No, the analogy still holds with name changes, because references can change. `JohnWayne = MarionMorrison; MarionMorrison = null;`. Now we only use the `JohnWayne` reference and not the `MarionMorrison` one. – Jon Hanna Jan 25 '12 at 20:44
  • You are absolutely right; my consideration was the following: If we are talking about an object oriented language, then 'A list of People' already implies 'A list of pointers to memory structures that represent people'. One would not need to call it 'A list of pointers to People' – The Nail Jan 25 '12 at 20:53
  • Thanks for the answer. And yes, my question was a bit philosophical. And as such I disagree with you on that aspect. A list of primary colors is an immutable list or that is the way I see it, a list of persons is not, because inherently people are mutable (you could also argue that dying = Remove() ;) ). But still, no reason arguing about the issue as its open to interpretation. About the value types, my bad, I was not considering mutable reference fields inside structs as I tend to avoid them whenever I can. – InBetween Jan 25 '12 at 21:07
  • 1
    @InBetween That's supposing that there's never any value in keeping a list of mutable references immutable. You can't offer the same guarantees if its immutable all the way down, but just knowing that the list won't change has made at least one thing that little bit easier to reason about. Immutability is a restriction - it adds something we can't do - and all restrictions make things easier to reason about - which increases the ultimate range of what you can do, because you've moved another step further from "I dunno, its all just a bunch of 1s and 0s moving about" :) – Jon Hanna Jan 25 '12 at 21:20
  • @JonHanna: Related to that is the fact that code which, at some level of abstraction, has more than one "nested" level of mutable reference, is often *much* harder to reason about, especially in multi-threading situations, than code which only has one. – supercat Feb 24 '12 at 20:19
  • @EricLippert "I note that this is not a very efficient way to implement an immutable list. You'd likely do better with an immutable balanced binary tree, for example. Your sketch is O(n) in both time and memory every time you make a new list; you can improve that to O(log n) without too much difficulty." Would you be so kind and explain how you can achieve log(n)? – Bober02 Apr 06 '12 at 16:00
  • @Bober02 read Eric's blog series on the subject: http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/ – phoog Apr 06 '12 at 18:43
5

Immutability is sometimes defined in different ways. So is thread-safety.

In creating a immutable list whose purpose is to be immutable, you should document just what guarantees you are making. E.g. in this case you guarantee that the list itself is immutable and does not have any hidden mutability (some apparently immutable objects are actually mutable behind the scenes, with e.g. memoisation or internal re-sorting as an optimisation) which removes the thread-safety that comes from immutability (though one can also have such internal mutations performed in a manner that guarantees thread-safety in a different way). You are not guaranteeing that the objects stored can be used in a thread-safe manner.

The thread-safety that you should document relates to this. You can not guarantee that another object won't have the same object (you could if you were creating new objects on each call). You can guarantee that operations will not corrupt the list itself.

Insisting upon T : struct could help, as it would mean that you could ensure that each time you return an item, it's a new copy of the struct (T : struct alone wouldn't do that, as you could have operations that didn't mutate the list, but did mutate its members, so obviously you have to also do this).

This though limits you in both not supporting immutable reference types (e.g. string which tends to be a member of collections in lots of real-world cases) and doesn't allow a user to make use of it and provide their own means of ensuring that the mutability of the contained items doesn't cause problems. Since no thread-safe object can guarantee that all the code it is used in is thread-safe, there's little point tryint to ensure that (help as much as you can by all means, but don't try to ensure what you can't ensure).

It also doesn't protect mutable members of immutable structs in your immutable list!

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • If a mutable struct is stored in an immutable list, the struct will become immutable. That's one of the advantages of "mutable" structs over mutable classes--wrap them in a container that doesn't mutate them or pass them as 'ref' parameters to other methods which could do so, and they'll remain immutable. Of course, if the struct holds a reference to a mutable class type, the reference may be immutable, but the object referred to thereby may not. It's really too bad there's no ImmutableArray type nor any other way for a struct to hold a variable-sized solidly-immutable collection of objects. – supercat Feb 07 '12 at 23:16
  • @supercat It can be one of the disadvantages too, since the behaviour can be surprising. – Jon Hanna Feb 08 '12 at 09:38
  • Confusion could have been avoided if either struct field access used a different character from class field access (analogous to the `->` versus `.` in C), or if naming conventions for struct types and storage locations were different from those of classes. Back when C# allowed code to uselessly write structs in read-only contexts, the possibilities for confusion were enormous, but now that C# disallows writing fields or properties of read-only structs, the only "confusion" would be with programmers who don't know what structs are; the remedy is for such programmers to learn. – supercat Feb 08 '12 at 16:16
  • A struct-type storage locations with int fields X and Y holds two pieces of information. The value of X and the value of Y. A class-type storage location with two fields X and Y would effectively hold three pieces of information: the values of X and Y, and the set of all other storage locations which hold the same reference. Sometimes one needs to have objects that can be associated with each other--for such purposes, classes are clearly appropriate. Often, though, managing such associations is mental and electronic overhead that would best be avoided. – supercat Feb 08 '12 at 16:23
  • BTW, even though a "phone number" would be considered a candidate for a value type, which would seem like the more natural operation: "Take all the phone numbers with area code "708" and exchange "475" and change the area code to "630", or "Take all the phone numbers with area code "708" and exchange "475" and replace them with new phone numbers with area code "630", the original exchange, the original base number, the original extension, etc.? I would suggest that many real-world "values" are often modified piecemeal. – supercat Feb 08 '12 at 16:30
2

Using your code, let's say i do this:

ImmutableList<int> mylist = new ImmutableList<int>();

mylist.Add(1); 

... your code, posted on StackOverflow, causes a StackOverflow-Exception. There are quite a few sensible ways to create thread save collection, copying collections (at least trying to) and calling them immutable, a lot, doesn't quite do the trick.

Eric Lippert posted a link that might be very worth reading.

Mithrandir
  • 24,869
  • 6
  • 50
  • 66
  • @InBetween: Still, copy-on-write isn't the way to go about this! – Mithrandir Jan 25 '12 at 21:11
  • Jon Hanna: The implementation of the immutable list is not the issue...I'm not considering implementing a list this way, it was just an example. THe issue is if `ImmutableList` is really an immutable list. – InBetween Jan 25 '12 at 21:18
  • @InBetween I deleted that comment because I was mis-reading anyway! Still, it's an immutable list of something that may or may not be mutable. This gives you some guarantees, and could (with a different implementation) be valuable in terms of thread-safety and in other ways too. – Jon Hanna Jan 25 '12 at 21:56
2

A prime example of a data type that behaves as an immutable list of mutable objects: MulticastDelegate. A MulticastDelegate may be modeled pretty accurately as an immutable list of (object, method) pairs. The set of methods, and the identities of the objects upon which they act are immutable, but in the vast majority of cases the objects themselves will be mutable. Indeed, in many if not most cases, the very purpose of the delegate will be to mutate the objects to which it holds references.

It is not the responsibility of a delegate to know whether the methods it's going to invoke upon its target objects might mutate them in thread-unsafe fashion. The delegate is responsible merely for ensuring that its lists of functions and object identities are immutable, and I don't think anyone would expect it to likewise.

An ImmutableList<T> should likewise always hold the same set of instances of type T. The properties of those instances might change, but not their identity. If a List<Car> was created holding two Fords, serial #1234 and #4422, both of which happened to be red, and one of the Fords was painted blue, what started out as a list of two red cars would have changed to a list holding a blue car and a red car, but it would still hold #1234 and #4422.

supercat
  • 77,689
  • 9
  • 166
  • 211
1

I believe that one thing adding to the rather lengthy discussion on this topic is that immutability/mutability should be considered along with scope.

As an example:

Say that I am working with a C# project and I define a static class with static data structures, and I define no way to modify those structures in my project. It is in effect a read-only cache of data that I can use, and for the purposes of my program, it is immutable from one run of my program to the next. I have total control over the data, and I/the user am/is unable to modify it at run time.

Now I modify my data in my source code and re-run the program. The data has changed, so in the highest sense of the word, the data is no longer immutable. From the highest level perspective, the data is actually mutable.

I would therefore posit that what we need to be discussing is not the black and white question blanketing something as immutable or not, but rather we should consider the degree of mutability of a particular implementation (as there are few things that actually never change, and are therefore truly immutable).

Michael Jordan
  • 382
  • 2
  • 13
1

I would say that generic list is not immutable if the element is mutable, because it does not represent the full snapshot of the data state.

To achieve immutability in your example you would have to create deep copies of list elements, which is not efficient to do every time.

You can see my solution to this problem at IOG library

nenadsabo
  • 11
  • 2