Question

I created a sort of string wrapper class and want to use its instances as dictionary keys interchangeably with usual strings. I overrode GetHashCode and Equals and got results that seem strange. I've isolated the issue. Please look at my code and explain me why does second lookup returns null.

void Main()
{
    var foo = new StringWrapper("foo");
    var h = new Hashtable {{ foo, "bar" }};
    Console.WriteLine(h["foo"]);
    Console.WriteLine(h[foo]); // null ??
}

public class StringWrapper
{
    readonly string wrapped;
    public StringWrapper(string s) {
        wrapped = s;
    }
    public override bool Equals(object obj) {
        return wrapped.Equals(obj);
    }
    public override int GetHashCode() {
        return wrapped.GetHashCode();
    }
    public override string ToString() {
        return wrapped;
    }
}
Was it helpful?

Solution

Your equality implementation isn't symmetric, nor reflexive:

StringWrapper wrapper = new StringWrapper("foo");

Console.WriteLine(wrapper.Equals(wrapper)); // False
Console.WriteLine(wrapper.Equals("foo")); // True
Console.WriteLine("foo".Equals(wrapper)); // False

So you're violating the rules specified in the docs for Object.Equals. Hashtable (and other classes) expect you not to violate those rules, and won't work properly when you violate them.

It sounds like you need a custom collection, rather than a wrapper which tries to pretend that the wrapper is equal to the original value.

(As an aside, why are you still using non-generic Hashtable rather than Dictionary<,>?)

OTHER TIPS

Keep in mind that in Equals(object obj), obj won't be a string, it will be a StringWrapper - and a string will never say that it is equal to something that is not a string. You need to extract the wrapped string from obj. Keep in mind that you should check for null and take into account that if someone uses StringWrapper for something else than putting it into a hash table, obj could also be any other type.

The problem is your Equals method:

public override bool Equals(object obj) {
    // wrapped is a String, but obj is (usually) a StringWrapper
    return wrapped.Equals(obj);
}

You need something like this:

public override bool Equals(object obj) {
    var other = obj as StringWrapper;
    return other != null && wrapped.Equals(other.wrapped);
}

EDIT: As per Jon's answer, you also might want to be able to compare to Strings:

public override bool Equals(object obj) {
    var other = obj as StringWrapper;
    return (other != null && wrapped.Equals(other.wrapped))
           || Object.Equals(wrapped, (obj as String));
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top