Dictionary doesn't find value when using new object as key

In Unity 3D, I am trying to build a Dictionary of PathNodes keyed by NodeCoordinates. I have overridden GetHashCode in NodeCoordinate and it should return a constant unique value. If I loop through the keys of the dictionary, the lookup works fine, but if I create a new NodeCoordinate with the coordinates of a PathNode that should exist, the lookup fails even though the hash codes are equal.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class PathNode : MonoBehaviour {

    public static Dictionary<NodeCoordinate, PathNode> pathNodes;

    public PathNode left;
    public PathNode right;
    public PathNode forward;
    public PathNode backward;

    private NodeCoordinate coord;

    void Awake()
    {
        if (PathNode.pathNodes == null)
        {
            PathNode.pathNodes = new Dictionary<NodeCoordinate, PathNode>();
        }

        NodeCoordinate coord = new NodeCoordinate(transform.position.x, transform.position.z);
        this.coord = coord;
        PathNode.pathNodes.Add(coord, this);

        NodeCoordinate leftChord = new NodeCoordinate(coord.x - 1, coord.z);
        NodeCoordinate rightChord = new NodeCoordinate(coord.x + 1, coord.z);
        NodeCoordinate forwardChord = new NodeCoordinate(coord.x, coord.z + 1);
        NodeCoordinate backwardChord = new NodeCoordinate(coord.x, coord.z - 1);

        if (PathNode.pathNodes.ContainsKey(leftChord))
        {
            this.left = PathNode.pathNodes[leftChord];
            this.left.right = this;
        }
        if (PathNode.pathNodes.ContainsKey(rightChord))
        {
            this.right = PathNode.pathNodes[rightChord];
            this.right.left = this;
        }
        if (PathNode.pathNodes.ContainsKey(forwardChord))
        {
            this.forward = PathNode.pathNodes[forwardChord];
            this.forward.backward = this;
        }
        if (PathNode.pathNodes.ContainsKey(backwardChord))
        {
            this.backward = PathNode.pathNodes[backwardChord];
            this.backward.forward = this;
        }
    }

    private static bool debug = true;

    void Update()
    {
        if (debug)
        {
            foreach (NodeCoordinate coord in PathNode.pathNodes.Keys)
            {
                Debug.Log(coord + " : " + PathNode.pathNodes[coord] + " : " + coord.GetHashCode());
            }

            foreach (PathNode node in PathNode.pathNodes.Values)
            {
                NodeCoordinate leftChord = new NodeCoordinate(node.coord.x - 1, node.coord.z);
                PathNode leftNode;
                Debug.Log("Left: " + leftChord + " : " + PathNode.pathNodes.TryGetValue(leftChord, out leftNode) + " : " + leftChord.GetHashCode());
            }

            debug = false;
        }
    }
}

public class NodeCoordinate
{
    public float x;
    public float z;

    public NodeCoordinate(float x, float z)
    {
        this.x = x;
        this.z = z;
    }

    public bool Equals(NodeCoordinate coord)
    {
        return (this.x == coord.x && this.z == coord.z);
    }

    public override int GetHashCode()
    {
        return string.Format("{0}x{1}", this.x, this.z).GetHashCode();
    }

    public override string ToString()
    {
        return "Coordinate: " + this.x + " x " + this.z;
    }
}

This is the output from my little debug: debug log

As you can see, when looping through the keys, the lookups with hashcodes 2137067561 and 1824497336 work, but when I instantiate a new NodeCoordinate and try to look it up, it has the same hashcode but the lookup fails. Any idea why this is happening? Thanks.

Jon Skeet
people
quotationmark

The problem is that your NodeCoordinate class doesn't define equality in a way that the dictionary would use. You have a method like this:

public bool Equals(NodeCoordinate coord)

... but you neither override IEquatable<NodeCoordinate> nor do you override Equals(object).

Personally I'd suggest doing both - and implementing a simpler hash code that doesn't require string formatting:

public sealed class NodeCoordinate : IEquatable<NodeCoordinate>
{
    public float X { get; }
    public float Z { get; }

    public NodeCoordinate(float x, float z)
    {
        X = x;
        Z = z;
    }

    public override Equals(object other) => Equals(other as NodeCoordinate);

    public bool Equals(NodeCoordinate coord) =>
        coord != null && this.X == coord.X && this.Z == coord.Z;

    public override int GetHashCode()
    {
        int hash = 23;
        hash = hash * 31 + X;
        hash = hash * 31 + Z;
        return hash;
    }

    public override string ToString() => $"Coodinate: {X} x {Z}";
}

(Note that I've also made it immutable and used properties instead of public fields. I've used C# 6 syntax for simplicity, but it wouldn't be hard to translate to C# 5 if necessary.)

people

See more on this question at Stackoverflow