DotGNU Portable.Net FAQ

Rhys Weatherley, rweather@southern-storm.com.au.
Last Modified: $Date: 2003/03/01 10:49:19 $

Copyright © 2003 Southern Storm Software, Pty Ltd.
Permission to distribute unmodified copies of this work is hereby granted.

Introduction

This document describes the design of Portable.NET's Generic C# collection library. The library is distributed under the terms of the X11/MIT License, which allows use by any application, both free and commercial.

The library was designed to have a similar "flavour" to the existing non-generic C# collections, with some ideas borrowed from the C++ Standard Template Library (STL), and the Java 2 Collections library.

Conventions

Notations such as ArrayList<T> are used to indicate generic classes; whereas notations such as ArrayList are used to indicate the traditional non-generic classes.

General design considerations

Here are some examples of the problems in the existing non-generic collection classes:

  1. It is possible to create read-only, fixed-size, and synchronized ArrayList's, but only synchronized Hashtable's.
  2. Common algorithms, such as sorting and searching, must be implemented in every concrete list implementation, when it would make more sense to have a common utility method that takes an IList.
  3. Ugly hacks, like wrapping an IList inside an ArrayList, to get around the algorithm limitations.
  4. Enumerators are read-only, failing if the underlying collection is modified. This makes it difficult to implement algorithms that require modify in-place, or the removal of items during traversal.
  5. Confusion between abstract and concrete implementations in some cases. e.g. there is a Queue class, but no IQueue interface. So, it isn't easy to replace an array based queue with a linked list based queue.
  6. Functionality is overridden through virtual methods, usually to allow read-only or type-safe wrapping. This can make the base implementation less efficient, due to the overhead of virtual method calls that will rarely point at anything except the base implementation.
  7. IsReadOnly and IsFixedSize are common collection traits like IsSynchronized, but appear in subinterfaces instead of ICollection.
To address these problems, we have the following design constraints:

  1. Read-only, fixed-size, and synchronized functionality are provided with separate decorator classes, not as part of the concrete implementations. e.g. ReadOnlyList<T>.
  2. Common algorithms are ejected to the Algorithm utility class as static generic methods.
  3. Concrete classes only implement interfaces - they never implement other utility functions beyond that.
  4. Iterators are introduced to replace enumerators, and support in-place removal. List iterators also support traversal backwards through a list and in-place modification of the current item.
  5. Concrete classes always implement at least one abstract interface representing a useful data structure (list, queue, stack, etc).
  6. Concrete classes are sealed. Modified functionality is provided using decorators.
  7. Common collection traits are placed into ICollection<T>.
We eschew the use of abstract base classes for implementations. e.g. Java 2's AbstractCollection. This is typically used to implement common utility methods (e.g. addAll() for copying one collection into another). Such utility methods are placed into the Algorithm class in our implementation.

An equivalent of the existing CollectionBase class is not needed in this implementation. It is typically used to enforce type safety constraints, which the generic language facilities are already taking care of for us. CollectionBase is also used to get notification of insertions, removals, etc. This can be accomplished using decorator classes instead.

Notes: the design of iterators is based on the Java 2 collection classes. The idea of ejecting algorithms and utility methods out of the concrete classes is borrowed from the C++ Standard Template Library.

Collection interfaces

ICollection<T>
Base interface for all collections.
IDeque<T>
The collection is organised as a deque (double-ended queue).
IDictionary<KeyT, ValueT>
The collection is organised as a dictionary, mapping keys to associated values.
IIterable<T>
The collection can be iterated over.
IList<T>
The collection is organised as a list, ordered on index. The IsRandomAccess property can be used to determine if the indexer implements constant-time lookups (e.g. ArrayList<T>) or non constant-time lookups (e.g. LinkedList<T>).
IQueue<T>
The collection is organised as a FIFO queue, where additions occur at one end, and removals from the other.
ISet<T>
The collection is organised as a set. Each value will occur at most once in the set.
IStack<T>
The collection is organised as a stack, where additions and removals occur at the same end.

Iterators

IIterator<T>
Provides a counterpart to the non-generic IEnumerator interface, with the addition of a Remove method. This method is used to remove the current item in the iteration in such a way that traversal can continue naturally.
IListIterator<T>
Extends IIterator<T> with the ability to traverse backwards through a list, to get the current position by index, and to modify the current item in-place.
IDictionaryIterator<T>
Extends IIterator<T> with properties that extract the key and value components, and modify the value of the current item in-place. There is no counterpart to the IDictionary.Entry property, because it is identical to Current.
If an iterator operation is called after the underlying collection has been modified, the behaviour is undefined. It may operate correctly, it may throw an exception, or it may appear to operate correctly but actually do something else. The only guarantees given are with respect to modifying Current and calling Remove on the iterator.

Utility interfaces

IComparable<T>
Provides a counterpart to the non-generic IComparable interface. Objects that implement this interface can be compared.
IComparer<T>
Provides a counterpart to the non-generic IComparer interface. It compares two values of type T.
IHashCodeProvider<T>
Provides a counterpart to the non-generic IHashCodeProvider interface. It takes a value of type T and returns a hash code, suitable for use in classes such as Hashtable<T>.
ICapacity
Provides an interface to get or set the capacity of a collection. Implemented by classes like ArrayList<T> that use an array to implement growable data structures.

Concrete implementations

ArrayList<T>
Implementation of an IList<T>, organised as an array.
ArrayQueue<T>
Implementation of an IQueue<T>, organised as an array.
ArrayStack<T>
Implementation of an IStack<T>, organised as an array.
Hashtable<KeyT, ValueT>
Implementation of an IDictionary<KeyT, ValueT>, organised as a hash.
LinkedList<T>
Implementation of an IList<T>, organised as a doubly-linked list. This class also implements IDeque<T>, IQueue<T>, and IStack<T>.
ListSet<T>
Implementation of an ISet<T> on top of an underlying IList<T> object. By default, a LinkedList<T> object is used.
SinglyLinkedList<T>
Implementation of an IList<T>, organised as a singly-linked list. This class also implements IQueue<T> and IStack<T>.
TreeSet<T>
Implementation of an ISet<T> as a balanced binary tree.
TreeDictionary<KeyT, ValueT>
Implementation of an IDictionary<KeyT, ValueT> as a balanced binary tree.

Decorators

FixedSizeCollection<T>
This class, and its descendents, are used to wrap up collections to make them appear to be fixed-size to callers. Any attempt to modify the size of the collection will result in an InvalidOperationException.
ReadOnlyCollection<T>
This class, and its descendents, are used to wrap up collections to make them appear to be read-only to callers. Any attempt to modify the contents of the collection will result in an InvalidOperationException.
SynchronizedCollection<T>
This class, and its descendents, are used to wrap up collections to synchronize access from multiple threads. The concrete collection classes are not safe to use simultaneously from multiple threads unless they are wrapped up in this fashion.
There are decorator classes for collections, deques, dictionaries, lists, queues, sets, and stacks. They all follow the above style.

Adapters and wrappers

Sometimes it can be useful to use a generic collection with an existing API that expects a non-generic collection, or to use an existing non-generic collection with a new API that expects a generic one. The following classes are provided for enabling interoperation between generic and non-generic libraries:

CollectionAdapter<T>
This class wraps a generic ICollection<T> and exports the non-generic ICollection interface.
CollectionWrapper<T>
This class wraps a non-generic ICollection and exports the generic ICollection<T> interface.
ComparableAdapter<T>
This class wraps a generic IComparable<T> and exports the non-generic IComparable interface.
ComparableWrapper<T>
This class wraps a non-generic IComparable and exports the generic IComparable<T> interface.
ComparerAdapter<T>
This class wraps a generic IComparer<T> and exports the non-generic IComparer interface.
ComparerWrapper<T>
This class wraps a non-generic IComparer and exports the generic IComparer<T> interface.
DictionaryAdapter<KeyT, ValueT>
This class wraps a generic IDictionary<KeyT, ValueT> and exports the non-generic IDictionary interface.
DictionaryWrapper<KeyT, ValueT>
This class wraps a non-generic IDictionary and exports the generic IDictionary<KeyT, ValueT> interface.
DictionaryEnumeratorAdapter<KeyT, ValueT>
This class wraps a generic IDictionaryIterator<KeyT, ValueT> and exports the non-generic IDictionaryEnumerator interface.
DictionaryEnumeratorWrapper<KeyT, ValueT>
This class wraps a non-generic IDictionaryEnumerator and exports the generic IDictionaryIterator<KeyT, ValueT> interface.
EnumerableAdapter<T>
This class wraps a generic IIterable<T> and exports the non-generic IEnumerable interface.
EnumerableWrapper<T>
This class wraps a non-generic IEnumerable and exports the generic IIterable<T> interface.
EnumeratorAdapter<T>
This class wraps a generic IIterator<T> and exports the non-generic IEnumerator interface.
EnumeratorWrapper<T>
This class wraps a non-generic IEnumerator and exports the generic IIterator<T> interface.
HashCodeProviderAdapter<T>
This class wraps a generic IHashCodeProvider<T> and exports the non-generic IHashCodeProvider interface.
HashCodeProviderWrapper<T>
This class wraps a non-generic IHashCodeProvider and exports the generic IHashCodeProvider<T> interface.
ListAdapter<T>
This class wraps a generic IList<T> and exports the non-generic IList interface.
ListWrapper<T>
This class wraps a non-generic IList and exports the generic IList<T> interface.
As can be seen, we use "*Adapter" classes to wrap generic collections for non-generic use, and use "*Wrapper" classes to wrap non-generic collections for generic use.

Miscellaneous classes

Algorithm
Provides a large number of generic utility methods for manipulating collections according to various algorithms (sort, search, etc).
BuiltinComparer<T>
Implementation of IComparer<T> which uses the builtin "<" and ">" operators to perform the comparison. This is only useful when T is a primitive numeric type.
Comparer<T>
Implementation of IComparer<T> which uses IComparable<T> and IComparable to perform the comparison.
Complex<T>
Implementation of a complex number type, based on an underlying numeric type T.
DictionaryEntry<KeyT, ValueT>
A value type that contains a key/value pair for use with dictionary collections and iterators.
Predicate<T>
Delegate that takes a single argument of type T and returns true if some predicate condition has been satisfied by the argument.
RangeList<T>
Wraps up an IList<T> to access a sub-range.
ReverseIterator<T>
Wraps up an IListIterator<T> to reverse the direction of traversal.
TreeBase<KeyT, ValueT>
Utility base class for TreeSet<KeyT> and TreeDictionary<KeyT, ValueT>. Not recommended for direct use by programmers.

Copyright © 2003 Southern Storm Software, Pty Ltd.
Permission to distribute unmodified copies of this work is hereby granted.