KeyedCollection<TKey,TItem> and DictionaryBase
KeyedCollection<TKey,TItem> subclasses Collection<TItem>. It both adds and sub-
tracts functionality. What it adds is the ability to access items by key, much like with
a dictionary. What it subtracts is the ability to proxy your own inner list.
A keyed collection has some resemblance to an OrderedDictionary in that it com-
bines a linear list with a hashtable. However, unlike OrderedDictionary, it doesn’t
implement IDictionary and doesn’t support the concept of a key/value pair. Keys
are obtained instead from the items themselves: via the abstract GetKeyForItem
method. This means enumerating a keyed collection is just like enumerating an
ordinary list.
KeyedCollection<TKey,TItem> is best thought of as Collection<TItem> plus fast
lookup by key.
Because it subclasses Collection<>, a keyed collection inherits all of Collection<>’s
functionality, except for the ability to specify an existing list in construction. The
additional members it defines are as follows:
public abstract class KeyedCollection <TKey, TItem> : Collection <TItem>
// ...
protected abstract TKey GetKeyForItem(TItem item);
protected void ChangeItemKey(TItem item, TKey newKey);
// Fast lookup by key - this is in addition to lookup by index.
public TItem this[TKey key] { get; }
protected IDictionary<TKey, TItem> Dictionary { get; }
}
GetKeyForItem is what the implementer overrides to obtain an item’s key from the
underlying object. The ChangeItemKey method must be called if the item’s key prop-
erty changes, in order to update the internal dictionary. The Dictionary property
returns the internal dictionary used to implement the lookup, which is created when
the first item is added. This behavior can be changed by specifying a creation
threshold in the constructor, delaying the internal dictionary from being created
until the threshold is reached (in the interim, a linear search is performed if an item
is requested by key). A good reason not to specify a creation threshold is that having
a valid dictionary can be useful in obtaining an ICollection<> of keys, via the
Dictionary’s Keys property. This collection can then be passed on to a public
property.
The most common use for KeyedCollection<,> is in providing a collection of items
accessible both by index and by name. To demonstrate this, we’ll revisit the zoo,
this time implementing AnimalCollection as a KeyedCollection<string,Animal>:
public class Animal
{
string name;
public string Name
{
get { return name; }
set {
if (Zoo != null) Zoo.Animals.NotifyNameChange (this, value);
name = value;
}
}
public int Popularity;
public Zoo Zoo { get; internal set; }
public Animal (string name, int popularity)
{
Name = name; Popularity = popularity;
}
}
public class AnimalCollection : KeyedCollection <string, Animal>
{
Zoo zoo;
public AnimalCollection (Zoo zoo) { this.zoo = zoo; }
internal void NotifyNameChange (Animal a, string newName)
{
this.ChangeItemKey (a, newName);
}
protected override string GetKeyForItem (Animal item)
{
return item.Name;
}
// The following methods would be implemented as in the previous example
protected override void InsertItem (int index, Animal item)...
protected override void SetItem (int index, Animal item)...
protected override void RemoveItem (int index)...
protected override void ClearItems()...
}
public class Zoo
{
public readonly AnimalCollection Animals;
public Zoo() { Animals = new AnimalCollection (this); }
}
class Program
{
static void Main()
{
Zoo zoo = new Zoo();
zoo.Animals.Add (new Animal ("Kangaroo", 10));
zoo.Animals.Add (new Animal ("Mr Sea Lion", 20));
Console.WriteLine (zoo.Animals [0].Popularity); // 10
Console.WriteLine (zoo.Animals ["Mr Sea Lion"].Popularity); // 20
zoo.Animals ["Kangaroo"].Name = "Mr Roo";
Console.WriteLine (zoo.Animals ["Mr Roo"].Popularity); // 10
}
}
DictionaryBase
The nongeneric version of KeyedCollection is called DictionaryBase. This legacy
class takes very different in its approach: it implements IDictionary and uses
clumsy hook methods like CollectionBase: OnInsert, OnInsertComplete, OnSet,
OnSetComplete, OnRemove, OnRemoveComplete, OnClear, and OnClearComplete (and ad-
ditionally, OnGet). The primary advantage of implementing IDictionary over taking
the KeyedCollection approach is that you don’t need to subclass it in order to obtain
keys. But since the very purpose of DictionaryBase is to be subclassed, it’s no ad-
vantage at all. The improved model in KeyedCollection is almost certainly due to
the fact that it was written some years later, with the benefit of hindsight.
DictionaryBase is best considered useful for backward compatibility.
No comments:
Post a Comment