BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Algorithms and Data Structures
.NET 2.0+

A Generic Read-Only Dictionary

The .NET framework includes several types of collection that are designed for use in object models. Amongst these is the ReadOnlyCollection that allows the creation of collections that may not be modified. However, there is no read-only Dictionary type.

Adding an Iterator

All dictionary classes implement an enumerator to allow the items in the collection to be traversed within foreach loops. For the read-only dictionary, we will implement two C# 2.0 iterators to support the generic and non-generic versions of the IEnumerable interfaces.

To add the first enumerator we will use the yield keyword within a loop through the items in the underlying collection. Each iteration of the loop will yield a KeyValuePair. Add the following method:

public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
    foreach (KeyValuePair<TKey, TValue> item in _dictionary)
    {
        yield return item;
    }
}

The second iterator will complete the implementation of the IEnumerable interface and will be implemented explicitly. This iterator simply returns the value of the previous one.

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

Adding the Implemented Methods

Some of the IDictionary methods will be fully supported by the ReadOnlyDictionary wrapper because they do not change the contents of the collection. These are the ContainsKey method, the TryGetValue method, the Contains method and the CopyTo method. In each case, no additional functionality over the methods from the underlying collection is required. To add the methods, add the following code to the class:

public bool ContainsKey(TKey key)
{
    return _dictionary.ContainsKey(key);
}

public bool TryGetValue(TKey key, out TValue value)
{
    return _dictionary.TryGetValue(key, out value);
}

public bool Contains(KeyValuePair<TKey, TValue> item)
{
    return _dictionary.Contains(item);
}

public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
    _dictionary.CopyTo(array, arrayIndex);
}

Adding an Indexer

The IDictionary interface defines an indexer for all dictionaries so that values can be retrieved and changed using the keys as references to the values. However, in the ReadOnlyDictionary we only want to permit reading of the values. To satisfy the requirements of the interface and to keep the public interface of the new class as elegant as possible, we will implement the indexer both implicitly and explicitly.

For the implicit indexer, which will be visible for the ReadOnlyDictionary itself, only the get accessor will be included. For this indexer, add the following code:

public TValue this[TKey key]
{
    get { return _dictionary[key]; }
}

The second variation upon the indexer will be implemented explicitly for the IDictionary interface. This will include both get and set accessors. However, as we do not wish to allow a value to be set, the set accessor will simply throw a NotSupportedException when called.

TValue IDictionary<TKey, TValue>.this[TKey key]
{
    get { return this[key]; }
    set { throw new NotSupportedException(); }
}

Keys and Values Properties

The IDictionary class includes several other properties that must be implemented. The first two of these return a full list of either the keys or the values from the dictionary in a collection that implements the ICollection interface. For the wrapped dictionary, these two collections can be retrieved but are not read-only.

To convert the Keys and Values properties of the underlying dictionary to be read-only, we must wrap them in ReadOnlyCollection objects. However, it is not possible to directly wrap an ICollection object in a read-only collection directly; we must perform two conversions. Firstly the collection is converted to any type that supports the IList interface. This list may then be wrapped. We will use generic List objects in the interim step.

public ICollection<TKey> Keys
{
    get
    {
        ReadOnlyCollection<TKey> keys = new ReadOnlyCollection<TKey>(
            new List<TKey>(_dictionary.Keys));
        return (ICollection<TKey>)keys;
    }
}

public ICollection<TValue> Values
{
    get
    {
        ReadOnlyCollection<TValue> values = new ReadOnlyCollection<TValue>(
            new List<TValue>(_dictionary.Values));
        return (ICollection<TValue>)values;
    }
}
28 February 2009