C# OOP – Enforcing Overriding of GetHashCode() and Equals() in Generic Container Class

cinterfacesobject-orientedobject-oriented-design

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!

Best Answer

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);