C # XNA: проблема со словарями - PullRequest
2 голосов
/ 08 февраля 2010

Я новичок в C #. Возможно, я не реализую IEquatable должным образом, потому что объекты моего типа, которые следует считать одинаковыми, не являются.

Класс:

class CompPoint : IComparable {
        public int X;
        public int Y;

        public CompPoint(int X, int Y) {
            this.X = X;
            this.Y = Y;
        }

   public override bool Equals(Object o) {
        if (!(o is CompPoint)) {
            throw new ArgumentException(String.Format("argument is not a CompPoint (%s given)", o));
        }
        CompPoint cp = (CompPoint)o;

        return this.X == cp.X && this.Y == cp.Y;
    }

    public override int GetHashCode() {
        int hash = base.GetHashCode(); // this is a problem. replace with a constant?
        hash = (hash * 73) + this.X.GetHashCode();
        hash = (hash * 73) + this.Y.GetHashCode();
        return hash;
    }
}

(Это нечто большее, чем CompPoint, которое оправдывает его как класс.)

Затем этот тест не пройден:

    [TestMethod()]
    public void compPointTest() {
        Assert.AreEqual(new CompPoint(0, 0), new CompPoint(0, 0));
    }

Что я неправильно понимаю? Assert.AreEqual() использует ссылочное равенство? Моя Equals() функция в CompPoint испорчена?

Эта функция также не срабатывает:

    public void EqualsTest() {
        Assert.IsTrue(new CompPoint(1, 1).Equals(new CompPoint(1, 1)));
    }

Причина этого в том, что я использую Dictionary, и он работает не так, как я надеюсь, что он будет:

    [TestMethod()]
    public void dictCompPointTest() {
        IDictionary<CompPoint, int> dict = new Dictionary<CompPoint, int>();
        dict[new CompPoint(0, 0)] = 4;
        dict[new CompPoint(0, 0)] = 24;
        dict[new CompPoint(0, 0)] = 31;

        Assert.AreEqual(31, dict[new CompPoint(0, 0)]);
        Assert.AreEqual(1, dict.Count);
    }

Тест не пройден с этим сообщением:

Метод испытания ShipAILabTest.BoardUtilsTest.dictCompPointTest бросил исключение: System.Collections.Generic.KeyNotFoundException: Данный ключ отсутствовал в словарь.

Этот тест отражает мои ожидания. Я надеюсь, что, поскольку ключ каждый раз идентичен, значение будет перезаписано. Что Dictionary использует для проверки на равенство?

ОБНОВЛЕНИЕ: Я добавил функцию равенства, согласно предложению Томаса, и теперь CompPoint тесты сравнения работают, и dictCompPointTest работает.

    public override bool Equals(Object o) {
        if (!(o is CompPoint)) {
            throw new ArgumentException(String.Format("argument is not a CompPoint (%s given)", o));
        }
        CompPoint cp = (CompPoint)o;

        return this.X == cp.X && this.Y == cp.Y;
    }

Таинственно, этот тест по-прежнему не проходит:

   [TestMethod()]
    public void dictCPTest2() {
        IDictionary<CompPoint, int> dict = new Dictionary<CompPoint, int>();
        dict[new CompPoint(2, 2)] = 2;
        dict[new CompPoint(2, 2)] = 2;

        Assert.AreEqual(1, dict.Count);
    }

Проверка также не проходит, если ключи new CompPoint(4, 1), но не когда ключи new CompPoint(0, 1). Почему это может работать для некоторых значений, а не для других?

Больше загадок: функция хеш-кода, кажется, работает довольно плохо. Этот тест не пройден:

    [TestMethod()]
    public void hashCodeTest() {
                int x = 0;
                int y = 0;
                Assert.AreEqual(new CompPoint(x, y).GetHashCode(), new CompPoint(x, y).GetHashCode());    
    }

Функция хеш-кода указана выше. В чем здесь проблема? Разве два CompPoint объекта не должны иметь одинаковый хеш-код? Может быть, мой звонок на base.getHashCode() является проблемой?

Ответы [ 2 ]

3 голосов
/ 08 февраля 2010

Я думаю, Assert.AreEqual просто использует Object.Equals, а не IEquatable<T>.Equals. Поэтому вам нужно переопределить Equals, чтобы отразить логику IEquatable<T>.Equals.

Или вы также можете использовать Assert.IsTrue:

IEquatable<CompPoint> p1 = new CompPoint(0, 0);
IEquatable<CompPoint> p2 = new CompPoint(0, 0);
Assert.IsTrue(p1.Equals(p2));

Обратите внимание, что я объявляю p1 и p2 как IEquatable<CompPoint>: это гарантирует, что IEquatable<CompPoint>.Equals вызывается, а не Object.Equals, поскольку интерфейс реализован явно

РЕДАКТИРОВАТЬ: кстати, вы можете объявить CompPoint как структуру, а не класс. Таким образом, вам даже не нужно ничего реализовывать, поскольку типы значений сравниваются в соответствии со значениями их полей

1 голос
/ 08 февраля 2010

Если вы переопределяете Equals, вам также следует переопределить GetHashCode, поскольку именно это словарь будет использовать в первом случае, чтобы определить, совпадают ли два ключа. (Любые два объекта одного и того же типа, которые считаются равными, должны возвращать одно и то же значение из GetHashCode.)

public class CompPoint : IEquatable<CompPoint>
{
    // ...

    public override bool Equals(object obj)    // object
    {
        return this.Equals(obj as ComPoint);
    }

    public bool Equals(CompPoint other)    // IEquatable<ComPoint>
    {
        return !object.ReferenceEquals(other, null)
            && this.X.Equals(other.X)
            && this.Y.Equals(other.Y);
    }

    public override int GetHashCode()    // object
    {
        int hash = 5419;
        hash = (hash * 73) + this.X.GetHashCode();
        hash = (hash * 73) + this.Y.GetHashCode();
        return hash;
    }
}
...