custom == for Dictionary extension считает, что они пусты

У меня есть (enum: int) AccessOptions, который используется для заполнения словаря, чтобы отразить простые свойства доступа для индексированного списка основных имен пользователей.

Мне нужно было поддерживать модульное тестирование, в частности

    Assert.AreEqual
    <Dictionary<string, AccessOptions>, 
    Dictionary<string, AccessOptions>)

... который запутан и вызывает общий оператор равенства Dictionary, который на самом деле проверяет только ссылочные значения - мне нужно установить, что два разных ссылочных объекта содержат одни и те же ключи по значению, и что связанные значения этих ключей совпадают в каждом кейс.

Итак, я написал свой собственный оператор равенства и создал класс, который расширяет Dictionary, чтобы не испортить объекты Dictionary в моем проекте. Когда этот метод выполняется в Visual Studio 2016 в контексте отладки вызова Assert.AreEqual юнит-теста, несколько вещей идут не так, и я отмечу их ниже с помощью комментариев.

        public class SecretAccessRuleSet : Dictionary<string, AccessOptions>
{
    public SecretAccessRuleSet() {}

    public SecretAccessRuleSet(int size) : base (size) {}

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        return obj.GetType() == GetType() && Equals((SecretAccessRuleSet) obj);
    }

    public static bool operator == (SecretAccessRuleSet a, SecretAccessRuleSet b)
    {
        if (ReferenceEquals(a, b))  
        { 
            return true; 
        }

        /* When the below statement executes in debug, I can watch 
         * execution flow from the equality test against (object)a,
         * straight to "return false" -- but execution does not actually
         * return, but appears to jump directly to the foreach statement
         * two blocks down.  It might be important, or just a bug.  
         */
        if (((object)a == null) || ((object)b == null))
        {
            return false;
        }

        if (a.Count != b.Count)
        {
            return false;
        }

        /* Then when we get here, the visualizer highlights a; I advance 
         * one line and it highlights "in", which I assume is wherein an 
         * enumerator is initialized; I advance again and we jump to the 
         * end of the method!  Literally to the end curly brace.  
         */
        foreach (var entry in a)
        {
            AccessOptions bOpt;
            if (!b.TryGetValue(entry.Key, out bOpt)
                || bOpt != entry.Value)
            {
                return false;
            }
        }

        // If we get here, they're equal
        return true;
    }

    public bool Equals(SecretAccessRuleSet other)
    {
        return this == other;
    }

    public static bool operator !=(SecretAccessRuleSet a, SecretAccessRuleSet b)
    {
        return !(a == b);
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}

Результаты теста показывают, что вызов Assert.AreEqual(a, b) вернул false, но я очень мало верю в то, что содержимое любой коллекции было оценено, и я не понимаю, почему.

Я, вероятно, удалю все это и создам отдельный способ тестирования без переопределения оператора, но что здесь пошло не так?

(Спасибо всем, за ваше время.)

Обновление: чтобы указать то, что я забыл, обе коллекции имеют ожидаемый тип, ненулевой, и обе содержат 2 записи — на самом деле одни и те же 2 записи; Я ожидал, что оператор равенства вернет true в тесте.

Обновление 2: я отделил нулевые проверки; "(object) a == null" оценивается как false и продолжается, но "(object) b == null" оценивается как true и отправляет выполнение на "return false" - но опять же, те же две проблемы, когда выполнение на самом деле не возвращает, но пытается перечислить первое... и b на самом деле не равно нулю. Есть ли причины, по которым b будет допустимым объектом, но операция приведения в (объект) b может завершиться неудачей?


person Niali    schedule 11.08.2016    source источник
comment
Во время отладки вы можете навести указатель мыши на a и просмотреть содержимое. Если он пустой, это ваша проблема.   -  person nhouser9    schedule 11.08.2016
comment
Как сказал nhouser9, проверьте, пусты ли они. Видя, как ваш (объект)a идет прямо к возврату false, возможно, он имеет значение null и не повторяется.   -  person Memfisto    schedule 11.08.2016
comment
Смотрите обновление - извините, я не уточнил. Обе коллекции относятся к ожидаемому типу, а не к нулевому, и содержат по 2 записи в каждой. (Как это бывает, они совпадают.)   -  person Niali    schedule 11.08.2016
comment
Вы также должны показать свой тестовый код...   -  person Alexei Levenkov    schedule 11.08.2016
comment
Как реализован != для AccessOptions?   -  person recursive    schedule 11.08.2016
comment
Вы рассматривали CollectionAssert.AreEqual? msdn.microsoft.com/en-us/library/ms243763.aspx   -  person Eris    schedule 11.08.2016
comment
@Eris: две коллекции равны, если они содержат одни и те же элементы в одном порядке и в одном количестве. Это не работает для сравнения словарей, потому что словари не имеют стабильного порядка.   -  person recursive    schedule 11.08.2016
comment
Хорошо, CollectionAssert.AreEquivalent: две коллекции эквивалентны, если они содержат одни и те же элементы в одинаковом количестве, но в любом порядке. Тогда только типы ключа и значения должны будут реализовать равенство.   -  person Eris    schedule 11.08.2016


Ответы (2)


Как насчет проверки на равенство? После проверки равенства ссылок, нулей и количества ключей вы можете использовать Except, чтобы увидеть, содержит ли один ключи, которых нет в другом. Если да, то они не равны.

Только если все они проходят, вам нужно увидеть, есть ли случаи, когда значения для одних и тех же ключей в обоих словарях не совпадают.

public override bool Equals(object obj)
{
    if (ReferenceEquals(this, obj)) return true;
    var other = obj as SecretAccessRuleSet;
    if (other == null) return false;
    if (Count != other.Count) return false;
    if (Keys.Except(other.Keys).Any()) return false;
    return Keys.All(k => string.Equals(this[k], other[k]));
}
person Scott Hannen    schedule 11.08.2016
comment
Мне также пришлось бы проверять значения в обоих словарях. Вся идея состоит в том, чтобы создать коллекции двумя методами и проверить, что результаты обоих идентичны. Однако я не думал об использовании Except. :) - person Niali; 12.08.2016
comment
Это то, что делает последняя проверка. - person Scott Hannen; 12.08.2016

Ответ был, как обычно, проще, чем я его придумал.

Компараторы объектов словаря не являются явно нечувствительными к регистру, и при создании ключа между двумя методами возникало несоответствие регистра. Переопределение конструкторов SecretAccessRuleSet для вызова базового конструктора с помощью StringComparer.OrdinalIgnoreCase устранило проблему.

Проблема с переключением фокуса отладки — это ошибка отображения, которая, как известно, влияет на модульные тесты в этом контексте; код на самом деле не выполнял выделенные строки. Из-за этого все выглядело куда более странным, чем было на самом деле.

Уроки выучены:

  • Опубликуйте весь задействованный код (кто-то мог бы обнаружить проблему с регистром, но я предположил, что подробности маршрутов создания выходят за рамки проблемы, и я не должен этого делать).

  • Обвиняйте плагины, прежде чем подозревать основную IDE — испытания, которым они подверглись, были неравными.

Спасибо всем.

person Niali    schedule 11.08.2016