Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design a mutable class that after it's consumed becomes immutable

Suppose that the scenario doesn't allow to implement an immutable type. Following that assumption, I'd like opinions / examples on how to properly design a type that after it's consumed, becomes immutable.

public class ObjectAConfig {

  private int _valueB;
  private string _valueA;
  internal bool Consumed { get; set; }

  public int ValueB {
    get { return _valueB; }
    set
    {
      if (Consumed) throw new InvalidOperationException();
      _valueB = value;
    }
  }

  public string ValueA {
    get { return _valueA; }
    set
    {
      if (Consumed) throw new InvalidOperationException();
      _valueA = value;
    }
  }
}

When ObjectA consumes ObjectAConfig:

public ObjectA {

  public ObjectA(ObjectAConfig config) {

    _config = config;
    _config.Consumed = true;
  }
}

I'm not satisfied that this simply works, I'd like to know if there's a better pattern (excluded, as said, making ObjectAConfig immutable by design from begin).

For example:

  • can make sense define a monad like Once<T> that allow the wrapped value to be initialized only once?

  • can make sense define a type that returns the type itself changing a private field?

like image 786
jay Avatar asked Feb 26 '13 12:02

jay


1 Answers

What you are implementing sometimes goes under the name "popsicle immutability" - i.e. you can freeze it. Your current approach will work - indeed I use that pattern myself in numerous places.

You can probably reduce some duplication via something like:

private void SetField<T>(ref T field, T value) {
    if (Consumed) throw new InvalidOperationException();
    field = value;
}
public int ValueB {
    get { return _valueB; }
    set { SetField(ref _valueB, value); }
}    
public string ValueA {
    get { return _valueA; }
    set { SetField(ref _valueA, value); }
}

There is another related approach, though: a builder. For example, taking your existing class:

public interface IConfig
{
    string ValueA { get; }
    int ValueB { get; }
}
public class ObjectAConfig : IConfig
{
    private class ImmutableConfig : IConfig {
        private readonly string valueA;
        private readonly int valueB;
        public ImmutableConfig(string valueA, int valueB)
        {
            this.valueA = valueA;
            this.valueB = valueB;
        }
    }
    public IConfig Build()
    {
        return new ImmutableConfig(ValueA, ValueB);
    }
    ... snip: implementation of ObjectAConfig
}

Here there is a truly immutable implementation of IConfig, and your original implementation. If you want the frozen version, call Build().

like image 100
Marc Gravell Avatar answered Nov 01 '22 17:11

Marc Gravell