5

I need a collection that behaves as Set and preserves order of element insertion.

Is there one or I'll have to implement it myself?

What would the best implementation be?

Konstantin Spirin
  • 20,609
  • 15
  • 72
  • 90

5 Answers5

2

It doesn't exist in .NET, but you can emulate it using a List and the Distinct LINQ extension method, which should preserve the order of the underlying List.

David Pfeffer
  • 38,869
  • 30
  • 127
  • 202
  • It is extremely frustrating isn't it? Same frustration in PowerShell which uses Hashtable for dictionaries which also loses insertion order. – Josh Mar 05 '10 at 04:13
  • I need fast indexing but can afford slow insertions so I'll resort to `List` and will write extension method `AddUnique` to achieve Set semantics. – Konstantin Spirin Mar 05 '10 at 07:37
  • 2
    LINQ's `Distinct` does preserve order as currently implemented, but the [MSDN documentation](http://msdn.microsoft.com/en-us/library/bb348436.aspx) specifically states it is unordered. Proceed with caution. – Jeff Sharp May 17 '13 at 19:40
2

Will an OrderedDictionary do what you want?

Although it is not generic (meaning everything it returns has to be cast or unboxed) and is a dictionary (not a set), it will behave the way you want. You could easily just use some arbitrary value like null or true as values and let the keys be the members of your set.

Here's a possible implementation:

public class OrderedSet : ICollection, IEnumerable
{
    OrderedDictionary dict = new OrderedDictionary();
    public void Add(object member)
    {
        dict.Add(member, null);
    }
    public void Clear()
    {
        dict.Clear();
    }
    public void CopyTo(Array array, int index)
    {
        for (int i = 0; i < Count; i++)
            array[i + index] = dict[i];
    }
    public bool Contains(object member)
    {
        return dict.Contains(member);
    }
    public void Insert(int index, object member)
    {
        dict.Insert(index, member, null);
    }
    public void Remove(object member)
    {
        dict.Remove(member);
    }
    public void RemoveAt(int index)
    {
        dict.RemoveAt(index);
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return dict.Keys.GetEnumerator();
    }

    public int Count { get { return dict.Count; } }
    public ICollection Members { get { return dict.Keys; } }
    bool ICollection.IsSynchronized { get { return dict.IsSynchronized; } }
    object ICollection.SyncRoot { get { return dict.SyncRoot; } }
}
Gabe
  • 84,912
  • 12
  • 139
  • 238
  • 1
    It will work, but he would have to basically use only the keys and not the values. Not ideal, but it'll get the job done. – David Pfeffer Mar 05 '10 at 04:22
1

It is easy to create one:

public class InsertionOrderSet<T> : KeyedCollection<T,T>
{
    protected override T GetKeyForItem(T item)
    {
        return item;
    }
}

Caveat: Inserting duplicate items via .Add(T) will result in ArgumentExceptions, which differs from, say, a HashSet<T> which will just return false in that case.

Evgeniy Berezovsky
  • 18,571
  • 13
  • 82
  • 156
0

I realize this is an old post, but I had a need for something similar recently, and thought this implementation might help if someone wants a generic sequence that maintains the order items are added (as well as lets you insert before and after any given item). I'm sure someone has more efficient ways to get this done, but this does the trick.

public class Sequence<T> : ICollection<T>
{
    private readonly SortedList<long, T> _baseList;

    public Sequence()
    {
        this._baseList = new SortedList<long, T>();
    }

    public IEnumerator<T> GetEnumerator()
    {
        return this._baseList.Values.GetEnumerator();
    }

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

    public void Add(T item)
    {
        this._baseList.Add(this._baseList.Count(), item);
    }

    public void AddAfter(T item, T newItem)
    {
        var currentIndex = this._baseList.IndexOfValue(item);
        if (currentIndex == _baseList.Count())
        {
            this.Add(newItem);
        }
        else
        {
            var itemsToMove = new SortedList<long, T>();
            var total = Count;
            for (var i = currentIndex + 1; i < total; i++)
            {
                itemsToMove.Add(i, _baseList[i]);
                _baseList.Remove(i);
            }

            this.Add(newItem);
            foreach (var itemToMove in itemsToMove)
            {
                this.Add(itemToMove.Value);
            }
        }
    }

    public void AddBefore(T item, T newItem)
    {
        var currentIndex = this._baseList.IndexOfValue(item);
        var itemsToMove = new SortedList<long, T>();
        var total = Count;
        for (var i = currentIndex; i < total; i++)
        {
            itemsToMove.Add(i, this._baseList[i]);
            _baseList.Remove(i);
        }

        this.Add(newItem);
        foreach (var itemToMove in itemsToMove.Values)
        {
            this.Add(itemToMove);
        }
    }

    public void Clear()
    {
        this._baseList.Clear();
    }

    public bool Contains(T item)
    {
        return this._baseList.ContainsValue(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        this._baseList.Values.CopyTo(array, arrayIndex);
    }

    public bool Remove(T item)
    {
        try
        {
            this._baseList.RemoveAt(this._baseList.IndexOfValue(item));
            return true;
        }
        catch
        {
            return false;
        }
    }

    public int Count
    {
        get
        {
            return this._baseList.Count();
        }
    }

    public bool IsReadOnly
    {
        get
        {
            return false;
        }
    }
}
Mitchell Lee
  • 456
  • 3
  • 14
0

List inCountryList = new ArrayList(); . . . Set countrySet = new LinkedHashSet( inCountryList );

LinkedHashSet doesn't allow duplication, nad maintain insertion order.