Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does this code depend on string interning to work?

Tags:

string

c#

I'm creating a key for a dictionary which is a structure of two strings. When I test this method in a console app, it works, but I'm not sure if the only reason it works is because the strings are being interned and therefore have the same references.

Foo foo1 = new Foo();
Foo foo2 = new Foo();
foo1.Key1 = "abc";
foo2.Key1 = "abc";
foo1.Key2 = "def";
foo2.Key2 = "def";

Dictionary<Foo, string> bar = new Dictionary<Foo, string>();
bar.Add(foo1, "found");

if(bar.ContainsKey(foo2))
    System.Console.WriteLine("This works.");
else
    System.Console.WriteLine("Does not work");

The struct is simply:

public struct Foo
{
    public string Key1;
    public string Key2;
}

Are there any cases which would cause this to fail or am I good to rely on this as a unique key?

like image 440
Nick Gotch Avatar asked Dec 17 '22 03:12

Nick Gotch


2 Answers

According to Microsoft's documentation you should always override GetHashCode() if you intend to use your own data structures as keys in HashTables otherwise you might not be safe.

"Objects used as a key in a Hashtable object must also override the GetHashCode method because those objects must generate their own hash code."

http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx

like image 70
rui Avatar answered Jan 05 '23 09:01

rui


As per my comment to rubim's answer above, here's an reference to how I think the struct itself should be implemented. Note the immutable fields that can only be initialized via the constructor.

public struct Foo
{
    private readonly string key1;

    private readonly string key2;

    public string Key1
    {
        get
        {
            return this.key1;
        }
    }

    public string Key2
    {
        get
        {
            return this.key2;
        }
    }

    public Foo(string key1, string key2)
    {
        this.key1 = key1;
        this.key2 = key2;
    }

    public static bool operator ==(Foo foo1, Foo foo2)
    {
        return foo1.Equals(foo2);
    }

    public static bool operator !=(Foo foo1, Foo foo2)
    {
        return !(foo1 == foo2);
    }

    public override bool Equals(object obj)
    {
        if (!(obj is Foo))
        {
            return false;
        }

        Foo foo = (Foo)obj;
        bool key1Equal = ((this.key1 == null) && (foo.Key1 == null))
            || ((this.key1 != null) && this.key1.Equals(foo.Key1));
        bool key2Equal = ((this.key2 == null) && (foo.Key2 == null))
            || ((this.key2 != null) && this.key2.Equals(foo.Key2));

        return key1Equal && key2Equal;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;

            hash = (23 * hash)
                + (this.key1 == null ? 0 : this.key1.GetHashCode());
            return (31 * hash)
                + (this.key2 == null ? 0 : this.key2.GetHashCode());
        }
    }

    public override string ToString()
    {
        return (this.key1 == null ? string.Empty : this.key1.ToString() + ",")
            + (this.key2 == null ? string.Empty : this.key2.ToString());
    }
}

Then, the way to use these would be as such:

    Foo foo1 = new Foo("abc", "def");
    Foo foo2 = new Foo("abc", "def");

    Dictionary<Foo, string> bar = new Dictionary<Foo, string>();
    bar.Add(foo1, "found");

    if (bar.ContainsKey(foo2))
    {
        Console.WriteLine("This works.");
    }
    else
    {
        Console.WriteLine("Does not work");
    }
like image 39
Jesse C. Slicer Avatar answered Jan 05 '23 10:01

Jesse C. Slicer