【问题标题】:Dictionary doesn't find value when using new object as key使用新对象作为键时字典找不到值
【发布时间】:2016-11-27 22:10:52
【问题描述】:

在 Unity 3D 中,我正在尝试构建一个由 NodeCoordinates 键入的 PathNodes 字典。我已经在 NodeCoordinate 中覆盖了 GetHashCode,它应该返回一个恒定的唯一值。如果我遍历字典的键,查找工作正常,但如果我使用应该存在的 PathNode 的坐标创建一个新的 NodeCoordinate,即使哈希码相等,查找也会失败。

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

这是我的小调试的输出: debug log

如您所见,在遍历键时,哈希码为 2137067561 和 1824497336 的查找有效,但是当我实例化一个新的 NodeCoordinate 并尝试查找它时,它具有相同的哈希码但查找失败。知道为什么会这样吗?谢谢。

【问题讨论】:

    标签: c# dictionary hash hashmap


    【解决方案1】:

    问题在于您的NodeCoordinate 类没有以字典使用的方式定义相等性。你有这样的方法:

    public bool Equals(NodeCoordinate coord)
    

    ...但您既不覆盖IEquatable&lt;NodeCoordinate&gt;,也不覆盖Equals(object)

    我个人建议两者都做 - 并实现一个不需要字符串格式化的更简单的哈希码:

    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}";
    }
    

    (请注意,我也将其设为不可变并使用属性而不是公共字段。为简单起见,我使用了 C# 6 语法,但如果需要,将其转换为 C# 5 并不难。)

    【讨论】:

      【解决方案2】:

      因此,由于某种原因,当我添加具有完全相同逻辑的 IEqualityComparer 时,它可以工作。

      将字典的声明改为:

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

      添加了这个:

      public class NodeCoordinateComparer : IEqualityComparer<NodeCoordinate>
      {
          public bool Equals(NodeCoordinate a, NodeCoordinate b)
          {
              return (a.x == b.x && a.z == b.z);
          }
      
          public int GetHashCode(NodeCoordinate coord)
          {
              return string.Format("{0}x{1}", coord.x, coord.z).GetHashCode();
          }
      
      
      }
      

      【讨论】:

      • 我的回答解释了“出于某种原因”。基本上字典不知道如何使用你的Equals 方法。
      • 你的equals应该是:a.Equals(b)。哈希将进行比较。现在你 equals 正在比较数字,而哈希正在比较可能导致失败的字符串。
      猜你喜欢
      • 1970-01-01
      • 2011-01-09
      • 2021-12-17
      • 2012-08-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多