0

I declared a mutable class which serves as a replacement for a tuple as key for a dictionary. The reason is serialization. Serialization works just fine. The following problem occurs, I get a "KeyNotFoundException" when using this class, but only if a new instance of this class is used for the look-up. To make this more clear, see the following class definition:

    public class STuple<T1, T2>    {
    public T1 Item1 { get; set; }
    public T2 Item2 { get; set; }


    public static implicit operator Tuple<T1, T2>(STuple<T1, T2> st)
    {
        return Tuple.Create(st.Item1, st.Item2);
    }

    public static implicit operator STuple<T1, T2>(Tuple<T1, T2> t)
    {
        return new STuple<T1, T2>()
        {
            Item1 = t.Item1,
            Item2 = t.Item2,
        };
    }

    public STuple()
    {
    }
    public STuple(T1 t1, T2 t2) : this()
    {
        Item1 = t1;
        Item2 = t2;
    }
}

And here is the sample program:

        Dictionary<Tuple<string, string>, double> TupleDic = new Dictionary<Tuple<string, string>, double>();
        TupleDic.Add(new Tuple<string, string>("Name1", "Name2"), 5);
        TupleDic.Add(new Tuple<string, string>("Name3", "Name4"), 10);

        Console.WriteLine("dict-Entry 1: {0}", TupleDic[new Tuple<string, string>("Name1", "Name2")]);
        Console.WriteLine("dict-Entry 2: {0}", TupleDic[new Tuple<string, string>("Name3", "Name4")]);


        Dictionary<STuple<string, string>, double> STupleDic = new Dictionary<STuple<string, string>, double>();
        STuple<string, string> STuple1 = new STuple<string, string>("Name1", "Name2");
        STuple<string, string> STuple2 = new STuple<string, string>("Name3", "Name4");
        STupleDic.Add(STuple1, 5);
        STupleDic.Add(STuple2, 10);


        //Still working
        Console.WriteLine();
        Console.WriteLine("Sdict-Entry 1: {0}", STupleDic[STuple1]);
        Console.WriteLine("Sdict-Entry 2: {0}", STupleDic[STuple2]);

        //Not working
        STuple<string, string> STuple3 = new STuple<string, string>("Name1", "Name2");
        STuple<string, string> STuple4 = new STuple<string, string>("Name3", "Name4");
        Console.WriteLine();
        Console.WriteLine("Sdict-Entry 1: {0}", STupleDic[STuple3]);
        Console.WriteLine("Sdict-Entry 2: {0}", STupleDic[STuple4]);

        Console.ReadKey();

The example using the normal tuple works just fine, but when I use my own class STuple it only works, if I use the exact same keys (same instance) as used for adding. I am a total beginner, is it possible that there is a problem because of some mixup with value-type and reference-type?

Really strange in my opinion, a look-up with foreach still works:

            Console.WriteLine();
        foreach (KeyValuePair<STuple<string, string>, double> s in STupleDic)
        {
            Console.WriteLine("Sdict-Entry 1: {0}", s.Value);
        }
Johannes
  • 161
  • 1
  • 6
  • 3
    You must override Equals and GetHashCode in STuple. – Ralf Apr 18 '16 at 09:45
  • @Johannes : Even though you've accepted my answer, I'd urge you to reconsider using mutable keys in a hashtable. Hashtables do not watch their keys for changes, so if you were to mutate something that's being used as a key in a hashtable, you'll corrupt the hashtable. Really easy to do... really really hard to debug. – spender Apr 18 '16 at 14:22
  • @spender: Thank you for the remark. The amount of data stored will remain fairly small, so that I hope, I can avoid this kind of problem. Furthermore, the keys should stay unaltered once i created this database. However, as I pointed out, I am a real beginner (coming from mechanical engineering, and trying to fix something together for my field of work), so always glad for suggestions. Do you have any suggestions, what is better suited for a multikeyed dictionary, which is serializable? Best regards. – Johannes Apr 18 '16 at 14:36
  • As long as you're sure that they won't change, you'll be alright. I'd be tempted to add a method to your `STuple` that freezes the object and prevents further changes, perhaps by setting a flag that causes the setters to throw if you try and change them after calling the freeze method. At least this way, you'll get an early heads-up if you try to change a frozen `STuple`. – spender Apr 19 '16 at 02:14

1 Answers1

3

CAVEAT: Implementing GetHashCode on a mutable structure is a recipe for disaster. Hashcodes have only one purpose, and that is to facilitate storage in hash-tables. Items used as keys in hash-tables should not mutate (any properties used to calculate the hash) as changing the hashcode causes irrecoverable corruption to the hash-table.

In order for items to work in hash-table like collections, they must implement equality and hashcode members. As such, you could (with thanks to Resharper):

public class STuple<T1, T2>
{
    public STuple()
    {
    }

    public STuple(T1 t1, T2 t2)
        : this()
    {
        Item1 = t1;
        Item2 = t2;
    }

    public T1 Item1 { get; set; }
    public T2 Item2 { get; set; }

    protected bool Equals(STuple<T1, T2> other)
    {
        return EqualityComparer<T1>.Default.Equals(Item1, other.Item1) &&
               EqualityComparer<T2>.Default.Equals(Item2, other.Item2);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != GetType()) return false;
        return Equals((STuple<T1, T2>) obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return (EqualityComparer<T1>.Default.GetHashCode(Item1)*397) ^
                   EqualityComparer<T2>.Default.GetHashCode(Item2);
        }
    }

    public static bool operator ==(STuple<T1, T2> left, STuple<T1, T2> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(STuple<T1, T2> left, STuple<T1, T2> right)
    {
        return !Equals(left, right);
    }


    public static implicit operator Tuple<T1, T2>(STuple<T1, T2> st)
    {
        return Tuple.Create(st.Item1, st.Item2);
    }

    public static implicit operator STuple<T1, T2>(Tuple<T1, T2> t)
    {
        return new STuple<T1, T2>
        {
            Item1 = t.Item1,
            Item2 = t.Item2
        };
    }
}
spender
  • 117,338
  • 33
  • 229
  • 351