Search code examples
c#immutabilityimmutable-collectionscollection-initializer

Supporting collection initialization syntax for immutable collections - init modifiers for methods?


I need to implement an immutable collection which supports collection initialization syntax. The problem is in order to support collection initialization syntax, the collection must provide appropriate public Add() methods. However, if the collection provides public Add() methods, it is no longer immutable.

Example of what I want to achieve:

    var element = new MyElement();
    var collection1 = new MyImmutableCollection { element };
    var collection2 = new MyImmutableCollection { "ABC" };

In order for this to work, I need MyImmutableCollection to implement IEnumerable and provide the Add() methods with appropriate signatures (more on collection initialization is here):

class MyElement
{
    public MyElement()
    {
    }

    public MyElement(string label)
    {
        Label = label;
    }

    public string Label { get; set; }
}

class MyImmutableCollection : IEnumerable<MyElement>
{
    readonly List<MyElement> list = new List<MyElement>();

    #region Collection initialization support which breaks immutability

    public void Add(MyElement element)
    {
        list.Add(element);
    }

    public void Add(string label)
    {
        Add(new MyElement(label));
    }

    #endregion

    public MyElement this[int index]
    {
        get { return list[index]; }
        set { list.Insert(index, value); }
    }

    #region IEnumerable support

    public IEnumerator<MyElement> GetEnumerator()
    {
        return list.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}

However, the existence of Add() methods breaks immutability. I wonder, if there any new way to achieve the goal? I saw this question on StackOverflow - Can I initialize a BCL immutable collection using braces? - and seems like at that moment there was no appropriate solution, but that question is quite old, and hopefully there are some new features available in C#? I've read about introduction of init modifiers for methods to provide object initialization for immutable objects (here), but seems like the feature was reverted. I thought maybe there is some feature which could make Add() methods usable for initialization only?

Update. I could probably just add a constructor to the immutable collection like new MyImmutableCollection( /* items */ ). However, using a constructor may involve verbose syntax especially when you need to pass several parameters for each element. An example of using a constructor:

var collectionA = new MyImmutableCollection(new (int code, string description)[] {
    ( 1, "One" ),
    ( 2, "Two" ),
});

An example of collection initialization syntax:

var collectionB = new MyImmutableCollection
{
    { 1, "One" },
    { 2, "Two" },
};

Collection initialization syntax is more concise and therefore preferable.

That's what we need to add to the class to support the construction and collection initialization syntax above:

public MyImmutableCollection(IEnumerable<(int code, string description)> collection)
{
    // omitted for brevity
}

public void Add(int code, string description)
{
    // omitted for brevity
}

Solution

  • You can't do it. You can implement it in a simple way like this:

    class MyImmutableCollection : IEnumerable<MyElement>
        {
            readonly List<MyElement> list = new List<MyElement>();
    
            public MyImmutableCollection(IEnumerable<MyElement> list)
            {
                this.list.AddRange(list);
            }
    
            private MyImmutableCollection()
            {
            }
    
            public MyElement this[int index]
            {
                get { return list[index]; }
            }
    
            public IEnumerator<MyElement> GetEnumerator()
            {
                return list.GetEnumerator();
            }
    
            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }
    

    And below is how to use:

    var list = new List<MyElement> { new MyElement() };
    MyImmutableCollection immutableList = new MyImmutableCollection(list);
    

    Also, you can add a extension method:

    public static MyImmutableCollection ToImmutableCollection(this IEnumerable<MyElement> elements)
    {
         MyImmutableCollection list = new MyImmutableCollection(elements);
         return list;
    }
    

    And use it as more simple:

     var list = new List<MyElement> { new MyElement() };
     list.ToImmutableCollection();
    

    Any way, you can use the library System.Collections.Immutable without implement, below is how to use it:

    ImmutableList.Create(new MyElement());
    

    Note: You can add it by Nuget link https://www.nuget.org/packages/System.Collections.Immutable