5

So we all realize the benefits of immutable types, particularly in multithreaded scenarios. (Or at least we should all realize that; see e.g. System.String.)

However, what I haven't seen is much discussion for creating immutable instances, specifically design guidelines.

For example, suppose we want to have the following immutable class:

class ParagraphStyle {
    public TextAlignment Alignment {get;}
    public float FirstLineHeadIndent {get;}
    // ...
}

The most common approach I've seen is to have mutable/immutable "pairs" of types, e.g. the mutable List<T> and immutable ReadOnlyCollection<T> types or the mutable StringBuilder and immutable String types.

To mimic this existing pattern would require the introduction of some type of "mutable" ParagraphStyle type which "duplicates" the members (to provide setters), and then provide a ParagraphStyle constructor which accepts the mutable type as an argument

// Option 1:
class ParagraphStyleCreator {
    public TextAlignment {get; set;}
    public float FirstLineIndent {get; set;}
    // ...
}

class ParagraphStyle {
    // ... as before...
    public ParagraphStyle (ParagraphStyleCreator value) {...}
}

// Usage:
var paragraphStyle = new ParagraphStyle (new ParagraphStyleCreator {
    TextAlignment = ...,
    FirstLineIndent = ...,
});

So, this works, supports code completion within the IDE, and makes things reasonably obvious about how to construct things...but it does seem fairly duplicative.

Is there a better way?

For example, C# anonymous types are immutable, AND allow using "normal" property setters for initialization:

var anonymousTypeInstance = new {
    Foo = "string",
    Bar = 42;
};
anonymousTypeInstance.Foo = "another-value"; // compiler error

Unfortunately, the closest way to duplicate these semantics in C# is to use constructor parameters:

// Option 2:
class ParagraphStyle {
    public ParagraphStyle (TextAlignment alignment, float firstLineHeadIndent,
            /* ... */ ) {...}
}

But this doesn't "scale" well; if your type has e.g. 15 properties, a constructor with 15 parameters is anything but friendly, and providing "useful" overloads for all 15 properties is a recipe for a nightmare. I'm rejecting this outright.

If we try to mimic anonymous types, it seems that we could use "set-once" properties in the "immutable" type, and thus drop the "mutable" variant:

// Option 3:
class ParagraphStyle {
    bool alignmentSet;
    TextAlignment alignment;

    public TextAlignment Alignment {
        get {return alignment;}
        set {
            if (alignmentSet) throw new InvalidOperationException ();
            alignment = value;
            alignmentSet = true;
        }
    }
    // ...
}

The problem with this is that it it's not obvious that properties can be set only once (the compiler certainly won't complain), and initialization isn't thread-safe. It thus becomes tempting to add a Commit() method so that the object can know that the developer is done setting the properties (thus causing all properties that haven't previously been set to throw if their setter is invoked), but this seems to be a way to make things worse, not better.

Is there a better design than the mutable/immutable class split? Or am I doomed to deal with member duplication?

jonp
  • 13,512
  • 5
  • 45
  • 60
  • I realize this may be hard to believe, but I'm working with an existing C# code base. Porting the entire thing to F# is not an option, and even if it were an option the MSR-SSLA license that the F# libraries are released under do not look appropriate for my use, specifically (ii)(c). – jonp Feb 04 '10 at 14:37
  • This seems duplicative of: http://stackoverflow.com/questions/263585/immutable-object-pattern-in-c-what-do-you-think – jonp Feb 04 '10 at 14:43
  • May not be an answer, but I've dealt with similar requirements in the past by implementing ISupportInitialize. After a call to EndInit() you become immutable, and throw when setters are called. Not compile-time safe, but documentation and throwing early and often helps. –  Oct 08 '10 at 19:29

1 Answers1

2

In a couple projects I was using fluent approach. I.e. most generic properties (e.g. name, location, title) are defined via ctor while others are updated with Set methods returning new immutable instance.

class ParagraphStyle {
  public TextAlignment Alignment {get; private set;}
  public float FirstLineHeadIndent {get; private set;}
  // ...
  public ParagraphStyle WithAlignment(TextAlignment ta) {
      var newStyle = (ParagraphStyle)MemberwiseClone();
      newStyle.TextAlignment = ta;
  }
  // ...
}

MemberwiseClone is ok here as soon our class is truly deeply immutable.

olegz
  • 1,189
  • 10
  • 20
  • 1
    While this works, and is useful, it too is not a scalable solution. If you need to set e.g. 15 values, the WithFoo() mutator methods result in 14 "temporary" instances being created before you have your final result. Depending on how "heavy" your type is, this could be a considerable amount of garbage. WithFoo() also doesn't support collection initializers, which might not be a huge loss but I love them dearly ;-). The Foo+FooBuilder idiom appears to be a reasonable compromise. – jonp Jun 13 '11 at 15:17
  • I tried this approach another time in one of recent projects and I completely agree, it's boring to create all these wrappers. I agree Builder is much more elegant and pretty much readable. Btw what about deeply nested structures? FP data structures promote lowest possible memory footprint by reusing unchanged members. – olegz Jul 12 '11 at 16:21
  • You can also provide a constructor of the fluent builder that takes the immutable object as argument to initialize the internal fields with it. So, you can use `WithFoo()` only on those `Foos` you need to modify :) – Ivaylo Slavov Mar 22 '13 at 20:44