Контракт равно () для .NET Dictionary / IDictionary против контракта равно () для Java Map - PullRequest
7 голосов
/ 13 октября 2010

Ностальгия по Collections.unmodifiableMap(), я внедрил оболочку IDictionary только для чтения на основе этого обсуждения , и мой модульный тест быстро столкнулся с проблемой:

Assert.AreEqual (backingDictionary, readOnlyDictionary);

терпит неудачу, даже если пары ключ-значение совпадают. Я немного поиграл, и это выглядит как минимум (спасибо Simonyi)

Assert.AreEquals (backingDictionary, new Dictionary<..> { /* same contents */ });

проходит.

Я быстро просмотрел документацию Dictionary и IDictionary, и, к моему удивлению, я не смог найти эквивалент Java Map контракт, что два Maps с равным entrySet()s должны быть равны. (Документы говорят, что Dictionary - не IDictionary - переопределяет Equals(), но не говорите, что делает это переопределение.)

Похоже, что равенство ключ-значение в C # является свойством конкретного класса Dictionary, а не интерфейса IDictionary. Это правильно? Это вообще верно для всего System.Collections рамки?

Если это так, мне было бы интересно прочитать некоторые обсуждения того, почему MS выбрал этот подход, а также о том, какой предпочтительный способ будет проверять равенство содержимого коллекции в C #.

И, наконец, я не возражаю против указания на проверенную ReadOnlyDictionary реализацию. :)


ETA: Чтобы было ясно, я не ищу предложения о том, как протестировать мою реализацию - это относительно тривиально. Я ищу руководство по , какой контракт должны выполнять эти тесты. И почему.


ETA: Люди, я знаю, IDictionary - это интерфейс, и я знаю, интерфейсы не могут реализовывать методы. То же самое в Java. Тем не менее, интерфейс Java Map документирует ожидаемое определенное поведение от метода equals(). Конечно, должны быть .NET-интерфейсы, которые делают подобные вещи, даже если среди них нет интерфейсов коллекций.

Ответы [ 6 ]

3 голосов
/ 13 октября 2010

Переопределение equals обычно выполняется только с классами, которые имеют степень семантики значения (например, string).Ссылочное равенство - это то, что людей чаще всего волнует большинство ссылочных типов и хорошее значение по умолчанию, особенно в случаях, которые могут быть не совсем понятными (это два словаря с одинаковыми парами ключ-значение, но разными сравнителями равенства [и, следовательно, добавлениемодна и та же дополнительная пара ключ-значение могла бы сделать их теперь разными] равными или нет?) или где часто не нужно искать равенство значений.

В конце концов, вы ищете случай, когда дваразные типы считаются равными.Переопределение равенства, вероятно, все равно подведет вас.

Тем более, что вы всегда можете создать свой собственный сравнитель равенства достаточно быстро:

public class SimpleDictEqualityComparer<TKey, TValue> : IEqualityComparer<IDictionary<TKey, TValue>>
{
    // We can do a better job if we use a more precise type than IDictionary and use
    // the comparer of the dictionary too.
    public bool Equals(IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y)
    {
        if(ReferenceEquals(x, y))
            return true;
        if(ReferenceEquals(x, null) || ReferenceEquals(y, null))
            return false;
        if(x.Count != y.Count)
            return false;
        TValue testVal = default(TValue);
        foreach(TKey key in x.Keys)
            if(!y.TryGetValue(key, out testVal) || !Equals(testVal, x[key]))
                return false;
        return true;
    }
    public int GetHashCode(IDictionary<TKey, TValue> dict)
    {
        unchecked
        {
            int hash = 0x15051505;
            foreach(TKey key in dict.Keys)
            {
                var value = dict[key];
                var valueHash = value == null ? 0 : value.GetHashCode();
                hash ^= ((key.GetHashCode() << 16 | key.GetHashCode() >> 16) ^ valueHash);
            }
            return hash;
        }
    }
}

Это не будет соответствовать всем возможным случаям, когда одинхочет сравнить словари, но тогда это была моя точка зрения.

Заполнение BCL «вероятно, что они имеют в виду» методами равенства было бы неприятностью, а не помощью.

2 голосов
/ 24 июня 2011

Для более поздних читателей, вот что мне сказали / удалось выяснить:

  1. Контракт для коллекций .NET, в отличие от коллекций Java, не включает никакого конкретного поведения для *Метод расширения 1004 * или GetHashCode().
  2. LINQ Enumerable.SequenceEqual() будет работать для упорядоченных коллекций, включая словари, которые представлены как IEnumerable<KeyValuePair>;KeyValuePair является структурой, а ее Equals метод использует отражение для сравнения содержимого.
  3. Enumerable предоставляет другие методы расширения, которые можно использовать для объединения проверки равенства содержимогоНапример, Union() и Intersect().

Я подхожу к мысли, что, какими бы удобными ни были методы Java, они могут быть не самой лучшей идеей, если мы говорим оизменяемые коллекции и типичная неявная семантика equals() - что два equal объекта взаимозаменяемы..NET не обеспечивает очень хорошую поддержку неизменяемых коллекций, но библиотека с открытым исходным кодом PowerCollections делает.

2 голосов
/ 13 октября 2010

Я бы предложил использовать CollectionAssert.AreEquivalent () из NUnit.Assert.AreEqual () действительно не предназначен для коллекций.http://www.nunit.org/index.php?p=collectionAssert&r=2.4

1 голос
/ 25 октября 2010

Похоже, что равенство ключ-значение в C # является свойством конкретного класса Dictionary, а не интерфейса IDictionary.Это правильно?В целом ли это верно для всей платформы System.Collections?

Если да, мне было бы интересно прочитать некоторые обсуждения того, почему MS выбрала такой подход

Я думаю, что это довольнопросто - IDictionary - это интерфейс, и интерфейсы не могут иметь никаких реализаций, а в мире .NET равенство двух объектов определяется с помощью метода Equals.Поэтому просто невозможно переопределить Equals для интерфейса IDictionary, чтобы он обладал «равенством ключ-значение».

1 голос
/ 13 октября 2010
public sealed class DictionaryComparer<TKey, TValue>
    : EqualityComparer<IDictionary<TKey, TValue>>
{
    public override bool Equals(
        IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y)
    {
        if (object.ReferenceEquals(x, y)) return true;
        if ((x == null) || (y == null)) return false;
        if (x.Count != y.Count) return false;

        foreach (KeyValuePair<TKey, TValue> kvp in x)
        {
            TValue yValue;
            if (!y.TryGetValue(kvp.Key, out yValue)) return false;
            if (!kvp.Value.Equals(yValue)) return false;
        }
        return true;
    }

    public override int GetHashCode(IDictionary<TKey, TValue> obj)
    {
        unchecked
        {
            int hash = 1299763;
            foreach (KeyValuePair<TKey, TValue> kvp in obj)
            {
                int keyHash = kvp.Key.GetHashCode();
                if (keyHash == 0) keyHash = 937;

                int valueHash = kvp.Value.GetHashCode();
                if (valueHash == 0) valueHash = 318907;

                hash += (keyHash * valueHash);
            }
            return hash;
        }
    }
}
0 голосов
/ 25 октября 2010

вы допустили большую ошибку в своем первоначальном посте.Вы говорили о методе Equals() в интерфейсе IDictionary.В этом суть!

Equals () - это виртуальный метод System.Object, который классы могут переопределять.Интерфейсы не реализуют методы вообще.Вместо этого экземпляры интерфейсов являются ссылочными типами, поэтому наследуются от System.Object и , потенциально , объявляя переопределение Equals().

Теперь точка ... System.Collections.Generic.Dictionary<K,V> делает не переопределить Equals.Вы сказали, что реализовали свой IDictionary по-своему и разумно переопределили Equals, но посмотрите на свой собственный код

Assert.AreEqual (backingDictionary, readOnlyDictionary); 

Этот метод в основном реализован как return backingDictionary.Equals(readOnlyDictionary), и снова вот в чем дело.

Метод Basic Equals () возвращает false, если два объекта являются экземплярами разных классов, вы не можете это контролировать.В противном случае, если два объекта относятся к одному и тому же типу, каждый элемент сравнивается с помощью отражения (только члены, а не свойства) с использованием подхода Equals() вместо == (то, что руководство называет «сравнение значений» вместо «Сравнение ссылок ")

Поэтому, во-первых, я не удивлюсь, если Assert.AreEqual (readOnlyDictionary,backingDictionary); удастся, потому что это вызовет пользовательский метод Equals.

У меня нет сомнений, что другиепользователи в этой теме работают, но я просто хотел объяснить вам, в чем была ошибка в вашем первоначальном подходе.Конечно, Microsoft лучше бы реализовать метод Equals, который сравнивает текущий экземпляр с любым другим экземпляром IDictionary, но, опять же, он вышел бы за пределы области действия класса Dictionary, который является общедоступным автономным классом и не предназначен дляединственная общедоступная реализация IDictionary.Например, когда вы определяете интерфейс, фабрику и защищенный класс, который реализует его в библиотеке, вы можете сравнить класс с другими экземплярами базового интерфейса, а не с самим классом, который не является общедоступным.

Я надеюсь, что помог вам.Приветствия.

...