views:

359

answers:

3

Hi folks,

I have a class derived from Dictionary. I need this class to simulate a HashSet, because Silverlight doesn't know HashSets and my classes make heavy use of HashSets. So I decided to exchange the HashSet with Dictionary. To further use my classes with all the HashSet-Objects, I try to make a custom HashSet class, that is derived from Dictionary and override all the relavant methods like the Add-method:

class HashSet<T> : Dictionary<T, object>
{

    public override void Add(T element)
    {
     base.Add(element, null);
    }
}

Now I need to enable the foreach-loop for my new HashSet-class. Obviously, my class returns a KeyValuePair in a foreach-loop, but I need T as return type. Can anyone tell me, what and how I need to override the Dictionary base class?

Thanks in advance, Frank

+11  A: 

I would strongly suggest that you don't derive from Dictionary in the first place. Use composition instead of inheritance. If you derive from Dictionary, people can use your class as a dictionary of key/value pairs instead of as a set.

So, you design your class with a dictionary inside it:

public sealed class DictionaryBackedSet<T> : IEnumerable<T>
{
    private readonly Dictionary<T, int> dictionary = new Dictionary<T, int>();

    public IEnumerator<T> GetEnumerator()
    {
        return dictionary.Keys;
    }

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

    public bool Add(T item)
    {
        if (Contains(item))
        {
            return false;
        }
        dictionary.Add(item, 0);
        return true;
    }

    public bool Contains(T item)
    {
        return dictionary.ContainsKey(item);
    }

    // etc
}

You might also want to create an empty struct as the value type argument for the dictionary:

public struct Empty {}

That may save a little memory. I wouldn't worry about it for now though - and if you compose the dictionary instead of inheriting from it, making that change later won't break anything :)

It would be nice if you could use System.Void for this purpose (i.e. use Dictionary<T, Void>), but C# won't let you do that :(

Jon Skeet
If not using your Empty struct, shouldn't the example use bit over int?
Stevo3000
Thanks Jon! I'll follow your suggestions and use composition instead of inheritance. Are the two GetEnumerator methods enough to enable foreach? I thought I need to implement (at least) Current, MoveNext and Reset.
Aaginor
@aaginor: That would be implementing `IEnumerator`. You don't need to do that, because you can use the sequence of keys returned by the dictionary itself. You're just piggy-backing.
Jon Skeet
(An alternative would be to use an iterator block. You rarely need to implement MoveNext/Reset/Current yourself these days :)
Jon Skeet
Ah, I see. What's now left is to emulate the UnionWith method of the original HashSet. Instead of doing this manually, I thought to use the Union<> method of the dictionary, but then I would need to have access to the dictonary of the object I want to add, right?
Aaginor
The only Union I'm aware of with existing dictionaries is Enumerable.Union, which isn't really what you want. Don't forget that within the class, you would have access to the dictionary of a different `DictionaryBackedSet`, if that helps...
Jon Skeet
Further to Jon's comment regarding `Union`, it would be easy to implement the `UnionWith` method yourself anyway: `public void UnionWith(IEnumerable<T> other) { foreach (T item in other) { Add(item); } }`
LukeH
(You'll also need to add a check to ensure that the `other` parameter isn't null.)
LukeH
+1  A: 

I think you would need to implement IEnumerable(Of T), which would internally work with the Keys collection.

IDictionary already implements IEnumerable(Of KeyValuePair(Of TKey, TValue)), but perhaps it's possible to implement it for another type as well?

I'm with Jon re trying to subclass Dictionary--avoid if you can, composition is a much more appropriate design pattern.

richardtallent
Keys collection, not Values collection. The Values collection would just be all null (or 0, or whatever).
Jon Skeet
Edited, totally missed that in his code.
richardtallent
+2  A: 

I suggest that rather than inheriting from Dictionary you wrap it. A HashSet is not a Dictionary.

Matt Howells