Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Immutable objects with object initialisers

Tags:

c#

I have the following attempt at an immutable object:

class MyObject
{
    private static int nextId;

    public MyObject()
    {
        _id = ++nextId;
    }

    private int _id;
    public int Id { get { return _id; } }
    public string Name { get; private set; }
}

Then, I try to use it like so:

MyObject o1 = new MyObject { Name = "foo" };

But the object initialiser fails because Name's setter is private. Is there a way around this, or do I have to choose between one or the other?

like image 662
Matthew Scharley Avatar asked Aug 20 '09 05:08

Matthew Scharley


People also ask

What is immutable object can you write immutable object?

Immutable Objects are those objects whose state can not be changed once they are created, for example the String class is an immutable class. Immutable objects can not be modified so they are also thread safe in concurrent execution.

Is Builder pattern immutable?

The builder pattern hides the complexities of creating a class. In the case of an immutable class, it can be used to avoid constructors with too many parameters. Since the builder is not immutable, the values can be set through multiple calls.

Can immutable objects be modified?

An immutable object cannot be modified after it was created.

Do immutable objects have setters?

Now, we have an immutable class, but, it does contain a setter. This setter actively changes a member of the (final) field p.


2 Answers

You can't use object initializers with immutable objects. They require settable properties.

An immutable object implies "does not change after creation". Making Name a constructor parameter neatly expresses that principle.

If the object gets too complicated for a comprehensible constructor, you can also use the Builder pattern. Generally, the builder itself will have mutable properties (that you can use in object initializers), and its .Build() method will create the actual instance.

EDIT (OP): I'm going to add my own example of a builder that I cooked up here, then accept this answer since it proposes a reasonable solution.

class MyObject
{
    public class Builder
    {
        public Builder()
        {
            // set default values
            Name = String.Empty;
        }

        public MyObject Build()
        {
            return new MyObject(Name);
        }
        public string Name { get; set; }
    }

    private static int nextId;

    protected MyObject(string name)
    {
        Id = ++nextId;
        Name = name;
    }

    public int Id { get; private set; }
    public string Name { get; private set; }
}

You can then construct an instance of it with the following:

MyObject test = new MyObject.Builder { Name = "foo" }.Build();

EDIT: This is my take on the pattern:

public abstract class Builder<T>
{
    public static implicit operator T(Builder<T> builder)
    {
        return builder.Build();
    }

    private bool _built;

    public T Build()
    {
        if(_built)
        {
            throw new InvalidOperationException("Instance already built");
        }

        _built = true;

        return GetInstance();
    }

    protected abstract T GetInstance();
}

Here is your example as implemented with Builder<T>. It takes advantage of the scoping rules of nested types to access the private setter:

public class MyObject
{
    private static int nextId;

    protected MyObject()
    {
        Id = ++nextId;
    }

    public int Id { get; private set; }

    public string Name { get; private set; }

    public sealed class Builder : Builder<MyObject>
    {
        private MyObject _instance = new MyObject();

        protected override MyObject GetInstance()
        {
            // Validate properties here

            return _instance;
        }

        public string Name
        {
            get { return _instance.Name; }
            set { _instance.Name = value; }
        }
    }
}

It has an implicit conversion to the target type, allowing you to do this:

MyObject myObject = new MyObject.Builder { Name = "Some name" };

Or this:

public void Foo(MyObject myObject)

// ...

Foo(new MyObject.Builder { Name = "Some name" });
like image 134
Bryan Watts Avatar answered Sep 19 '22 15:09

Bryan Watts


You need to set the property in the constructor, and you don't need a separate local variable for the id:

class MyObject {

   private static int nextId = 0;

   public MyObject(string name) {
      Id = ++nextId;
      Name = name;
   }

   public int Id { get; private set; }
   public string Name { get; private set; }
}

Creation:

MyObject o1 = new MyObject("foo");
like image 37
Guffa Avatar answered Sep 19 '22 15:09

Guffa