1

I have a container class similar to the one below (with much of the logic omitted):

class Container<T>
{
    Dictionary<T, TWrapped> contains = new Dictionary<T, TWrapper>();

    public void Add(T item)
    {
        TWrapped wrappedItem = new TWrapped(item);
        contains[item] = wrappedItem;

        // logic involving wrappedItem...
    }

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

    // Item has been updated, so Sort called on container
    public void Sort(T item)
    {
        TWrapped wrappedItem;
        if(contains.TryGetValue(item, out wrappedItem))
        {
            // sort the wrappedItem within the container...
        }
    }

}

Is there some way of either telling users of Container that T will be used as a dictionary key or, equivalently, of forcing users to have overridden GetHashCode() and Equals() ?

Further Info

My actual container class implements a heap implicitly with an array. To do so, items are wrapped with a class that contains index information for finding parents and children within the array. This wrapper class is private within the heap to avoid unintended tampering with the index values.

I now wish to support re-sorting of specific items in the heap; however, to map from the user supplied item to its wrapped representation in the array, I need a dictionary to store such a mapping... hence the GetHashCode() Equals() woes.

Any criticisms of the design would be welcome!

  • 2
    GetHashCode and Equals are implemented on every Object by default. Eg. reference equality for classes and value equality for structs. It should be on the user of your class to decide if default equality is valid or not. – Euphoric Jan 21 '16 at 20:58
  • I don't understand the reasoning behind your question. There is no need to override Equals and GetHashCode in order to use a type as a key to a Dictionary. – David Arno Jan 21 '16 at 21:00
  • @Euphoric How can they properly decide if they aren't made aware of the dictionary implementation? That leads me to one of two conlcusions: either hiding it (as I have done) is bad, or users must always override equality when using object containers, which seems extreme to me at least. – Jack Jan 21 '16 at 21:02
  • @Jack, the only case where this would be a problem is if they create a class/struct that implements an override of Equals/GetHashCode and doesn't properly implement it. That's not your problem. – David Arno Jan 21 '16 at 21:09
  • Okay... I think I must have overestimated the danger of collisions when not overriding these methods! – Jack Jan 21 '16 at 21:15
3

When it comes to reference types (classes), you're already ok since those have Equals and GetHashCode implemented properly even without overriding.

When it comes to value types (structs, enums, ...), these should always override Equals and GetHashCode in meaningful manner as well as be immutable as per MSDN design guidelines - struct should implement IEquatable<T>; and transitively IEquatable<T> should override GetHashCode and Equals (Notes to implementors part).


If you want to really express this requirement to the consumer, consider adding a following generic constraint:

public class Container<T> where T : IEquatable<T>

This will most probably only cause nuisance to consumers however, as they will be forced to needlessly implement IEquatable<T> on classes.


Another option is to force every consumer to supply an IEqualityComparer<T> to your Container<T> which will then be used by the Dictionary:

public class Container<T>
{
     private Dictionary<T, TWrapped> _contains;

     public Container(IEqualityComparer<T> comparer)
     {
         _contains = new Dictionary<T, TWrapped>(comparer);
     }

     // rest of implementation...
}

Presumably, most consumers will do this

var container = new Container<T>(EqualityComparer<T>.Default);

but it feels nice to somehow mirror the Dictionary's ctors for increased flexibility. If I wanted ordinal case-insensitive string keys, I could do

var container = new Container<string>(StringComparer.OrdinalIgnoreCase);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.