I wrote an ImmutableList
class some time ago :
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class ImmutableList : IList, IEquatable>
{
#region Private data
private readonly IList _items;
private readonly int _hashCode;
#endregion
#region Constructor
public ImmutableList(IEnumerable items)
{
_items = items.ToArray();
_hashCode = ComputeHash();
}
#endregion
#region Public members
public ImmutableList Add(T item)
{
return this
.Append(item)
.AsImmutable();
}
public ImmutableList Remove(T item)
{
return this
.SkipFirst(it => object.Equals(it, item))
.AsImmutable();
}
public ImmutableList Insert(int index, T item)
{
return this
.InsertAt(index, item)
.AsImmutable();
}
public ImmutableList RemoveAt(int index)
{
return this
.SkipAt(index)
.AsImmutable();
}
public ImmutableList Replace(int index, T item)
{
return this
.ReplaceAt(index, item)
.AsImmutable();
}
#endregion
#region Interface implementations
public int IndexOf(T item)
{
if (_items == null)
return -1;
return _items.IndexOf(item);
}
public bool Contains(T item)
{
if (_items == null)
return false;
return _items.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
if (_items == null)
return;
_items.CopyTo(array, arrayIndex);
}
public int Count
{
get
{
if (_items == null)
return 0;
return _items.Count;
}
}
public IEnumerator GetEnumerator()
{
if (_items == null)
return Enumerable.Empty().GetEnumerator();
return _items.GetEnumerator();
}
public bool Equals(ImmutableList other)
{
if (other == null || this._hashCode != other._hashCode)
return false;
return this.SequenceEqual(other);
}
#endregion
#region Explicit interface implementations
void IList.Insert(int index, T item)
{
throw new InvalidOperationException();
}
void IList.RemoveAt(int index)
{
throw new InvalidOperationException();
}
T IList.this[int index]
{
get
{
if (_items == null)
throw new IndexOutOfRangeException();
return _items[index];
}
set
{
throw new InvalidOperationException();
}
}
void ICollection.Add(T item)
{
throw new InvalidOperationException();
}
void ICollection.Clear()
{
throw new InvalidOperationException();
}
bool ICollection.IsReadOnly
{
get { return true; }
}
bool ICollection.Remove(T item)
{
throw new InvalidOperationException();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
#region Overrides
public override bool Equals(object obj)
{
if (obj is ImmutableList)
{
var other = (ImmutableList)obj;
return this.Equals(other);
}
return false;
}
public override int GetHashCode()
{
return _hashCode;
}
#endregion
#region Private methods
private int ComputeHash()
{
if (_items == null)
return 0;
return _items
.Aggregate(
983,
(hash, item) =>
item != null
? 457 * hash ^ item.GetHashCode()
: hash);
}
#endregion
}
All methods that modify the collection return a modified copy. In order to fulfill with the IList
interface contract, the standard Add/Remove/Delete/Clear methods are implemented explicitly, but they throw an InvalidOperationException
.
This class uses a few non-standard extension methods, here they are :
public static class ExtensionMethods
{
public static IEnumerable Append(this IEnumerable source, T item)
{
return source.Concat(new[] { item });
}
public static IEnumerable SkipFirst(this IEnumerable source, Func predicate)
{
bool skipped = false;
foreach (var item in source)
{
if (!skipped && predicate(item))
{
skipped = true;
continue;
}
yield return item;
}
}
public static IEnumerable SkipAt(this IEnumerable source, int index)
{
return source.Where((it, i) => i != index);
}
public static IEnumerable InsertAt(this IEnumerable source, int index, T item)
{
int i = 0;
foreach (var it in source)
{
if (i++ == index)
yield return item;
yield return it;
}
}
public static IEnumerable ReplaceAt(this IEnumerable source, int index, T item)
{
return source.Select((it, i) => i == index ? item : it);
}
}
And here's a helper class to create instances of ImmutableList
:
public static class ImmutableList
{
public static ImmutableList CreateFrom(IEnumerable source)
{
return new ImmutableList(source);
}
public static ImmutableList Create(params T[] items)
{
return new ImmutableList(items);
}
public static ImmutableList AsImmutable(this IEnumerable source)
{
return new ImmutableList(source);
}
}
Here's a usage example :
[Test]
public void Test_ImmutableList()
{
var expected = ImmutableList.Create("zoo", "bar", "foo");
var input = ImmutableList.Create("foo", "bar", "baz");
var inputSave = input.AsImmutable();
var actual = input
.Add("foo")
.RemoveAt(0)
.Replace(0, "zoo")
.Insert(1, "bar")
.Remove("baz");
Assert.AreEqual(inputSave, input, "Input collection was modified");
Assert.AreEqual(expected, actual);
}
I can't say it's production quality, as I haven't tested it thoroughly, but so far it seems to work just fine...