31

Since immutability is not fully baked into C# to the degree it is for F#, or fully into the framework (BCL) despite some support in the CLR, what's a fairly complete solution for (im)mutability for C#?

My order of preference is a solution consisting of general patterns/principles compatible with

  • a single open-source library with few dependencies
  • a small number of complementary/compatible open-source libraries
  • something commercial

that

  • covers Lippert's kinds of immutability
  • offers decent performance (that's vague I know)
  • supports serialization
  • supports cloning/copying (deep/shallow/partial?)
  • feels natural in scenarios such as DDD, builder patterns, configuration, and threading
  • provides immutable collections

I'd also like to include patterns you as the community might come up with that don't exactly fit in a framework such as expressing mutability intent through interfaces (where both clients that shouldn't change something and may want to change something can only do so through interfaces, and not the backing class (yes, I know this isn't true immutability, but sufficient):

public interface IX
{
    int Y{ get; }
    ReadOnlyCollection<string> Z { get; }
    IMutableX Clone();
}

public interface IMutableX: IX
{
    new int Y{ get; set; }
    new ICollection<string> Z{ get; } // or IList<string>
}

// generally no one should get ahold of an X directly
internal class X: IMutableX
{
    public int Y{ get; set; }

    ICollection<string> IMutableX.Z { get { return z; } }

    public ReadOnlyCollection<string> Z
    {
        get { return new ReadOnlyCollection<string>(z); }
    }

    public IMutableX Clone()
    {
        var c = MemberwiseClone();
        c.z = new List<string>(z);
        return c;
    }

    private IList<string> z = new List<string>();       
}

// ...

public void ContriveExample(IX x)
{
    if (x.Y != 3 || x.Z.Count < 10) return;
    var c= x.Clone();
    c.Y++;
    c.Z.Clear();
    c.Z.Add("Bye, off to another thread");
    // ...
}
Community
  • 1
  • 1
Kit
  • 20,354
  • 4
  • 60
  • 103
  • 4
    This is a rather open-ended question, but interesting nonetheless. – Polynomial Oct 05 '11 at 14:47
  • I agree. Very broad, but enough broad appeal that I think it should stay open. – N_A Oct 05 '11 at 14:48
  • 1
    Most of the issues I have with immutable types and C# can't be solved with a plain library but would require a code generator. – CodesInChaos Oct 05 '11 at 14:49
  • I admit it's somewhat open-ended, but it helps me from re-inventing the wheel slightly differently for each project and might help others... – Kit Oct 05 '11 at 15:26
  • 7
    Note that there are other aspects to immutability that I didn't even consider. Consider for example the classic immutable queue that consists of two immutable stacks. There is one expensive operation when one of the stacks has to be reversed; executing that operation over and over again on that one "bad" immutable queue is expensive. It is possible to design immutable objects that amortize the costs of even "bad" operations, and sometimes that is necessary. See Chris Okasaki's book for a detailed explanation. – Eric Lippert Oct 05 '11 at 15:30
  • A few months ago I came across http://code.google.com/p/functional-dotnet/ . No idea how good it is though. – CodesInChaos Oct 05 '11 at 18:57
  • It seems that you can't even trust the framework. A ReadOnlyCollection http://msdn.microsoft.com/en-us/library/ms132474.aspx is only kind of readonly... http://alicebobandmallory.com/articles/2011/01/01/lazy-evaluation-is-no-friend-of-mutable-state – Jonas Elfström Oct 05 '11 at 21:57
  • @EricLippert: Personally, I think, that for the such an "immutable" objects you can always create *internal mutable methods* for "workaround" the *externally immutable data structures*, like you say, when you have to reverse a stack of immutable data. See my post and code sample here: http://stackoverflow.com/questions/4720475/why-can-struct-change-their-own-fields/4720966#4720966 – Alan Turing Oct 05 '11 at 22:19
  • 1
    @ArturMustafin: Often you can, yes. However, it is important to note that doing so is potentially dangerous. People who think that an "immutable" object is *automatically threadsafe* might get a nasty surprise when an internal-to-the-object mutation turns out to be not threadsafe after all. – Eric Lippert Oct 05 '11 at 22:54
  • I'm curious: is there an actual problem you're trying to solve? – John Saunders Oct 16 '11 at 19:08
  • @JohnSaunders - let's just say a class of problems. I'm constantly needing immutability for one reason or other. For example during multi-threading scenarios, presenting value objects in a DDD architecture, and for fluent builders that need mutability during build-up, then present the read-only interface to the world (configuration, for example). – Kit Apr 12 '12 at 15:23

4 Answers4

5

Would the better solution be to just use F# where you want true immutability?

Chris Marisic
  • 32,487
  • 24
  • 164
  • 258
  • 1
    it might look that way but F# is far from being immutable - as long as you build upon the .net framework and the CLR you will not get real immutability. It allways ends with being forced to use some base-types inside your abstractions that are not immutable. – Random Dev Oct 05 '11 at 19:01
  • 2
    Well, that same problem applies to C#, even more so. I think Chris was suggesting the use of F#, where at least then immutability is a major part of the language, whereas in C# it's merely a footnote. – Judah Gabriel Himango Oct 05 '11 at 19:11
  • 1
    @JudahHimango yes exactly. One of my biggest pet peeves with .NET is the aberration of string it has. The string type is terrible. It's a reference type that masquerades as a primitive type by employing pseudo-immutability. I wish they would fix it, but Microsoft would never have the *audacity* to make programmers update all their string usages to string? where they want a nullable string. Then we could do away with String.Empty and String.IsNullOrEmpty() nonsense. I can always hope atleast. – Chris Marisic Oct 05 '11 at 19:37
  • 2
    I've never worried about the string type. It's an unusual type in that it's a reference type that looks like a value, but that's merely a reflection of programmer usage of strings: when we change them, we want new ones (immutable), but when we pass them around, we want to pass them by reference. I think they did the right thing by making it an immutable type that looks like a value. (Truthfully speaking, with unsafe code, .NET strings can be mutated, but that's an extreme case.) – Judah Gabriel Himango Oct 05 '11 at 19:52
  • It does shine there (mostly), but my looking to a C# solution is for both in-house and extension developers who might tend to be less F# oriented. – Kit Oct 05 '11 at 21:40
3

Use this T4 template I put together to solve this problem. It should generally suit your needs for whatever kinds of immutable objects you need to create.

There's no need to go with generics or use any interfaces. For my purposes, I do not want my immutable classes to be convertible to one another. Why would you? What common traits should they share that means they should be convertible to one another? Enforcing a code pattern should be the job of a code generator (or better yet, a nice-enough type system to allow you to do define general code patterns, which C# unfortunately does not have).

Here's some example output from the template to illustrate the basic concept at play (nevermind the types used for the properties):

public sealed partial class CommitPartial
{
    public CommitID ID { get; private set; }
    public TreeID TreeID { get; private set; }
    public string Committer { get; private set; }
    public DateTimeOffset DateCommitted { get; private set; }
    public string Message { get; private set; }

    public CommitPartial(Builder b)
    {
        this.ID = b.ID;
        this.TreeID = b.TreeID;
        this.Committer = b.Committer;
        this.DateCommitted = b.DateCommitted;
        this.Message = b.Message;
    }

    public sealed class Builder
    {
        public CommitID ID { get; set; }
        public TreeID TreeID { get; set; }
        public string Committer { get; set; }
        public DateTimeOffset DateCommitted { get; set; }
        public string Message { get; set; }

        public Builder() { }

        public Builder(CommitPartial imm)
        {
            this.ID = imm.ID;
            this.TreeID = imm.TreeID;
            this.Committer = imm.Committer;
            this.DateCommitted = imm.DateCommitted;
            this.Message = imm.Message;
        }

        public Builder(
            CommitID pID
           ,TreeID pTreeID
           ,string pCommitter
           ,DateTimeOffset pDateCommitted
           ,string pMessage
        )
        {
            this.ID = pID;
            this.TreeID = pTreeID;
            this.Committer = pCommitter;
            this.DateCommitted = pDateCommitted;
            this.Message = pMessage;
        }
    }

    public static implicit operator CommitPartial(Builder b)
    {
        return new CommitPartial(b);
    }
}

The basic pattern is to have an immutable class with a nested mutable Builder class that is used to construct instances of the immutable class in a mutable way. The only way to set the immutable class's properties is to construct a ImmutableType.Builder class and set that in the normal mutable way and convert that to its containing ImmutableType class with an implicit conversion operator.

You can extend the T4 template to add a default public ctor to the ImmutableType class itself so you can avoid a double allocation if you can set all the properties up-front.

Here's an example usage:

CommitPartial cp = new CommitPartial.Builder() { Message = "Hello", OtherFields = value, ... };

or...

CommitPartial.Builder cpb = new CommitPartial.Builder();
cpb.Message = "Hello";
...
// using the implicit conversion operator:
CommitPartial cp = cpb;
// alternatively, using an explicit cast to invoke the conversion operator:
CommitPartial cp = (CommitPartial)cpb;

Note that the implicit conversion operator from CommitPartial.Builder to CommitPartial is used in the assignment. That's the part that "freezes" the mutable CommitPartial.Builder by constructing a new immutable CommitPartial instance out of it with normal copy semantics.

James Dunne
  • 3,607
  • 3
  • 24
  • 29
  • 1
    Pattern selection + codegen enforcement of it + a framework of immutable types and non-codegen patterns seems like the leading candidate. You've offered up 2 of the 3 nicely, thanks! Some cool points on your approach 1) it's an almost-language-feature in that `new X.Builder()` is like a factory method + initializer syntax. 2) It's a decent alternative to interface-based mutability enforcement. Now to find the framework piece... – Kit Oct 06 '11 at 13:28
  • I use the `new X.Builder(...)` form with C# 4's explicitly-named parameters and it works out very nicely, e.g. `new X.Builder(pField1: value1, pField2: value2, ...)`. I'm not sure what "framework piece" you think is missing? I have my own framework and I removed what minor tendrils it had in this T4 template. It was just stuff for generating strongly-typed identifier structs, computing SHA-1 identifiers, and other such things. – James Dunne Oct 07 '11 at 00:00
  • Basically immutable stacks, queues, and other structures. – Kit Oct 07 '11 at 13:43
2

Personally, I'm not really aware of any third party or previous solutions to this problem, so my apologies if I'm covering old ground. But, if I were going to implement some kind of immutability standard for a project I was working on, I would start with something like this:

public interface ISnaphot<T>
{
    T TakeSnapshot();
}

public class Immutable<T> where T : ISnaphot<T>
{
    private readonly T _item;
    public T Copy { get { return _item.TakeSnapshot(); } }

    public Immutable(T item)
    {
        _item = item.TakeSnapshot();
    }
}

This interface would be implemented something like:

public class Customer : ISnaphot<Customer>
{
    public string Name { get; set; }
    private List<string> _creditCardNumbers = new List<string>();
    public List<string> CreditCardNumbers { get { return _creditCardNumbers; } set { _creditCardNumbers = value; } }

    public Customer TakeSnapshot()
    {
        return new Customer() { Name = this.Name, CreditCardNumbers = new List<string>(this.CreditCardNumbers) };
    }
}

And client code would be something like:

    public void Example()
    {
        var myCustomer = new Customer() { Name = "Erik";}
        var myImmutableCustomer = new Immutable<Customer>(myCustomer);
        myCustomer.Name = null;
        myCustomer.CreditCardNumbers = null;

        //These guys do not throw exceptions
        Console.WriteLine(myImmutableCustomer.Copy.Name.Length);
        Console.WriteLine("Credit card count: " + myImmutableCustomer.Copy.CreditCardNumbers.Count);
    }

The glaring deficiency is that the implementation is only as good as the client of ISnapshot's implementation of TakeSnapshot, but at least it would standardize things and you'd know where to go searching if you had issues related to questionable mutability. The burden would also be on potential implementors to recognize whether or not they could provide snapshot immutability and not implement the interface, if not (i.e. the class returns a reference to a field that does not support any kind of clone/copy and thus cannot be snapshot-ed).

As I said, this is a start—how I'd probably start—certainly not an optimal solution or a finished, polished idea. From here, I'd see how my usage evolved and modify this approach accordingly. But, at least here I'd know that I could define how to make something immutable and write unit tests to assure myself that it was.

I realize that this isn't far removed from just implementing an object copy, but it standardizes copy vis a vis immutability. In a code base, you might see some implementors of ICloneable, some copy constructors, and some explicit copy methods, perhaps even in the same class. Defining something like this tells you that the intention is specifically related to immutability—I want a snapshot as opposed to a duplicate object because I happen to want n more of that object. The Immtuable<T> class also centralizes the relationship between immutability and copies; if you later want to optimize somehow, like caching the snapshot until dirty, you needn't do it in all implementors of copying logic.

Kit
  • 20,354
  • 4
  • 60
  • 103
Erik Dietrich
  • 6,080
  • 6
  • 26
  • 37
  • Interesting. You're making immutability very explicit (a plus on the *Intent* axis), but I don't think this is quite complete. On first glance, `Copy` is expensive because you also made a copy in `Immutable`'s constructor. If you eliminate the redundant snapshot and just return `_item` that helps. But regardless, you're always returning `T`, which we'll assume itself may be mutable. – Kit Oct 05 '11 at 17:42
  • Certainly not complete. It's an interesting problem that I'll have to think on further. Not having a reliable immutability paradigm is one of those problems I never realized I had until reading this thread, and now it's going to eat at me a little. – Erik Dietrich Oct 05 '11 at 19:09
1

If the goal is to have objects which behave as unshared mutable objects, but which can be shared when doing so would improve efficiency, I would suggest having a private, mutable "fundamental data" type. Although anyone holding a reference to objects of this type would be able to mutate it, no such references would ever escape the assembly. All outside manipulations to the data must be done through wrapper objects, each of which holds two references:

  1. UnsharedVersion--Holds the only reference in existence to its internal data object, and is free to modify it
  2. SharedImmutableVersion--Holds a reference to the data object, to which no references exist except in other SharedImmutableVersion fields; such objects may be of a mutable type, but will in practice be immutable because no references will ever be made available to code that would mutate them.

One or both fields may be populated; when both are populated, they should refer to instances with identical data.

If an attempt is made to mutate an object via the wrapper and the UnsharedVersion field is null, a clone of the object in SharedImmutableVersion should be stored in UnsharedVersion. Next, SharedImmutableCVersion should be cleared and the object in UnsharedVersion mutated as desired.

If an attempt is made to clone an object, and SharedImmutableVersion is empty, a clone of the object in UnsharedVersion should be stored into SharedImmutableVersion. Next, a new wrapper should be constructed with its UnsharedVersion field empty and its SharedImmutableVersion field populated with the SharedImmutableVersion from the original.

It multiple clones are made of an object, whether directly or indirectly, and the object hasn't been mutated between the construction of those clones, all clones will refer to the same object instance. Any of those clones may be mutated, however, without affecting the others. Any such mutation would generate a new instance and store it in UnsharedVersion.

supercat
  • 77,689
  • 9
  • 166
  • 211