HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpModerate

ImmutableList implementation in C#

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
implementationimmutableliststackoverflow

Problem

I tried to implement an immutable list.

public class ImmutableList : IEnumerable
{
    public T Head { get; set; }
    public ImmutableList Tail { get; set; }

    protected ImmutableList()
    {

    }

    public static ImmutableList WithItems(IEnumerable items)
    {
        if (items.Count() == 1)
        {
            return new ImmutableList()
            {
                Head = items.Single(),
                Tail = new EmptyList()
            };
        }

        return new ImmutableList()
        {
            Head = items.First(),
            Tail = WithItems(items.Skip(1))
        };
    }

    public ImmutableList Add(T item)
    {
        if (this is EmptyList)
        {
            return new ImmutableList() {Head = item, Tail = new EmptyList()};
        }

        return new ImmutableList()
        {
            Head = Head,
            Tail = Tail.Add(item)
        };
    }

    public IEnumerator GetEnumerator()
    {
        if (this is EmptyList) yield break;
        else
        {
            yield return Head;
            foreach (var elem in Tail)
                yield return elem;
        }
    }

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

public sealed class EmptyList : ImmutableList
{

}

public static class ImmutableListExtensions
{
    public static ImmutableList ToImmutableList(this IEnumerable sequence)
    {
        return ImmutableList.WithItems(sequence);
    } 
}


Simple example of how to use:

private static void Main(string[] args)
{
    ImmutableList list = new[] {1, 2, 3}.ToImmutableList();

    ImmutableList list2 = new EmptyList().Add(1).Add(2).Add(3).Add(4);

    foreach (var n in list)
    {
        Console.WriteLine(n);
    }
}


Is there a better and/or more efficient way to do this?

Solution

First of all, this class is not immutable at all. You can modify both the head and the tail at any given node in the list from calling code. Here are some quick fixes to make the list immutable:

public class ImmutableList : IEnumerable
{
    private static readonly ImmutableList _Empty = new EmptyList();

    private readonly T _Head;

    private readonly ImmutableList _Tail;

    public static ImmutableList Empty
    {
        get
        {
            return _Empty;
        }
    }

    public T Head
    {
        get
        {
            return this._Head;
        }
    }

    public ImmutableList Tail
    {
        get
        {
            return this._Tail;
        }
    }

    protected ImmutableList(T head, ImmutableList tail)
    {
        this._Head = head;
        this._Tail = tail;
    }

    public static ImmutableList WithItems(IEnumerable items)
    {
        return (items == null) || (!items.Any())
            ? _Empty
            : (items.Count() == 1
                ? new ImmutableList(items.Single(), _Empty)
                : new ImmutableList(items.First(), WithItems(items.Skip(1))));
    }

    public ImmutableList Add(T item)
    {
        return this == _Empty
            ? new ImmutableList(item, _Empty)
            : new ImmutableList(this._Head, this._Tail.Add(item));
    }

    public IEnumerator GetEnumerator()
    {
        if (this == _Empty)
        {
            yield break;
        }

        yield return this._Head;
        foreach (var elem in this._Tail)
        {
            yield return elem;
        }
    }

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

    private sealed class EmptyList : ImmutableList
    {
        public EmptyList() : base(default(TU), null)
        {
        }
    }
}


The calling code needs to change ever so slightly to accommodate:

var list2 = ImmutableList.Empty.Add(1).Add(2).Add(3).Add(4);


You'll also note I added a null and empty sequence check in the WithItems static method. This simply allows any null/empty sequence call to return an empty list.

Code Snippets

public class ImmutableList<T> : IEnumerable<T>
{
    private static readonly ImmutableList<T> _Empty = new EmptyList<T>();

    private readonly T _Head;

    private readonly ImmutableList<T> _Tail;

    public static ImmutableList<T> Empty
    {
        get
        {
            return _Empty;
        }
    }

    public T Head
    {
        get
        {
            return this._Head;
        }
    }

    public ImmutableList<T> Tail
    {
        get
        {
            return this._Tail;
        }
    }

    protected ImmutableList(T head, ImmutableList<T> tail)
    {
        this._Head = head;
        this._Tail = tail;
    }

    public static ImmutableList<T> WithItems(IEnumerable<T> items)
    {
        return (items == null) || (!items.Any())
            ? _Empty
            : (items.Count() == 1
                ? new ImmutableList<T>(items.Single(), _Empty)
                : new ImmutableList<T>(items.First(), WithItems(items.Skip(1))));
    }

    public ImmutableList<T> Add(T item)
    {
        return this == _Empty
            ? new ImmutableList<T>(item, _Empty)
            : new ImmutableList<T>(this._Head, this._Tail.Add(item));
    }

    public IEnumerator<T> GetEnumerator()
    {
        if (this == _Empty)
        {
            yield break;
        }

        yield return this._Head;
        foreach (var elem in this._Tail)
        {
            yield return elem;
        }
    }

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

    private sealed class EmptyList<TU> : ImmutableList<TU>
    {
        public EmptyList() : base(default(TU), null)
        {
        }
    }
}
var list2 = ImmutableList<int>.Empty.Add(1).Add(2).Add(3).Add(4);

Context

StackExchange Code Review Q#86276, answer score: 13

Revisions (0)

No revisions yet.