【问题标题】:custom == for Dictionary extension thinks they're empty字典扩展的自定义 == 认为它们是空的
【发布时间】:2016-08-11 00:24:53
【问题描述】:

我有一个 (enum : int) AccessOptions,它用于填充字典以反映用户主体名称索引列表的简单访问属性。

我需要支持单元测试——特别是

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

...这很混乱,并调用了真正只检查引用值的字典泛型的相等运算符——我需要确定两个不同的引用对象按值包含相同的键,并且这些键的关联值在每种情况下匹配。

所以,我编写了自己的相等运算符,并创建了一个扩展 Dictionary 的类,以避免在我的项目中搞砸 Dictionary 对象。当在 Visual Studio 2016 中在调试单元测试的 Assert.AreEqual 调用的上下文中执行此方法时,有一些事情发生了变化,我将在下面使用 cmets 标记它们。

        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" - 但同样,执行的两个问题相同实际上并没有返回,而是尝试首先枚举 a ......而 b 实际上并不为空。是否有原因 b 将是一个有效的对象,但 (object) b 中的强制转换操作可能会失败?

【问题讨论】:

  • 调试时,您可以将鼠标悬停在a 上并查看内容。如果它是空的,那是你的问题。
  • 如 nhouser9 所说,检查它们是否为空。看到您的 (object)a 如何直接“返回 false”,可能是它为 null 并且不迭代。
  • 查看更新——抱歉我没有指定。这两个集合都是预期的类型,不为空,并且每个都有 2 个条目。 (碰巧,它们匹配。)
  • 您还应该显示您的测试代码...
  • @Eris:“如果两个集合具有相同顺序和数量的相同元素,则它们是相等的。”这不适用于字典比较,因为字典没有稳定的顺序。

标签: c# unit-testing generics operator-keyword


【解决方案1】:

这对你的平等检查有什么好处? 在检查了引用相等、空值和键的数量之后,您可以使用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]));
}

【讨论】:

  • 我还必须测试两个字典中的值。整个想法是通过两种方法创建集合并验证两者的结果是否相同。我没想过使用除此之外。 :)
  • 这是最后一次检查所做的。
【解决方案2】:

和往常一样,答案比我做的简单。

字典对象比较器不是明确不区分大小写的,两种方法在创建键时存在大小写不匹配。重新定义 SecretAccessRuleSet 构造函数以使用 StringComparer.OrdinalIgnoreCase 调用基本构造函数修复了该问题。

debug-focus-jumping 问题是一个已知会在此上下文中影响单元测试的显示错误;代码并没有真正执行突出显示的行。这让整个事情看起来比实际情况更奇怪。

经验教训:

  • 发布所有所涉及的代码(有人会发现案例问题,但我认为创建路线的详细信息超出了问题的范围,我不应该这样做那个)。

  • 在怀疑核心 IDE 之前先责怪插件 -- 他们接受的测试并不相同。

谢谢大家。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-07-08
    • 2014-05-30
    • 1970-01-01
    • 2012-02-18
    • 1970-01-01
    • 1970-01-01
    • 2011-06-23
    • 1970-01-01
    相关资源
    最近更新 更多