为什么具有隐式转换运算符的自定义结构上的Assert.AreEqual失败?

时间:2016-11-24 19:17:15

标签: c# unit-testing struct implicit-conversion

我创建了一个表示金额的自定义结构。它基本上是decimal的包装器。它有一个隐式转换运算符,可以将其强制转换回decimal

在我的单元测试中,我断言Amount等于原始十进制值,但测试失败。

[TestMethod]
public void AmountAndDecimal_AreEqual()
{
    Amount amount = 1.5M;

    Assert.AreEqual(1.5M, amount);
}

当我使用int时(我没有创建转换运算符),测试 成功。

[TestMethod]
public void AmountAndInt_AreEqual()
{
    Amount amount = 1;

    Assert.AreEqual(1, amount);
}

当我将AreEqual悬停时,会显示第一个解析为

public static void AreEqual(object expected, object actual);

,第二个导致

public static void AreEqual<T>(T expected, T actual);

看起来int1隐含地投射到Amount,而decimal1.5M则不是。{/ p>

我不明白为什么会这样。我本来希望恰恰相反。第一个单元测试应该能够将decimal转换为Amount

当我向int添加隐式强制转换(这没有意义)时,第二个单元测试也会失败。因此,添加隐式强制转换运算符会破坏单元测试。

我有两个问题:

  1. 这种行为的解释是什么?
  2. 如何修复Amount结构,以便两个测试都成功?
  3. (我知道我可以更改测试以进行显式转换,但如果我不是绝对必须,我不会)

    My Amount struct(只是显示问题的最小实现)

    public struct Amount
    {
        private readonly decimal _value;
    
        private Amount(decimal value)
        {
            _value = value;
        }
    
        public static implicit operator Amount(decimal value)
        {
            return new Amount(value);
        }
    
        public static implicit operator decimal(Amount amount)
        {
            return amount._value;
        }
    }
    

1 个答案:

答案 0 :(得分:6)

当您可以在两个方向转换implicit时,会发生不好的事情,这就是一个例子。

由于隐式转换,编译器能够以相同的值选择Assert.AreEqual<decimal>(1.5M, amount);Assert.AreEqual<Amount>(1.5M, amount);。*

由于它们相等,所以推理都不会引起过载。

由于推理选择没有超载,因此不会将其列入选择最佳匹配的列表中,只有(object, object)表单可用。所以这是被选中的人。

使用Assert.AreEqual(1, amount),因为存在从intAmount的隐式转换(通过隐式int-&gt;十进制),但没有从Amount隐式转换为{{ 1}}编译器认为&#34;显然它们意味着int在这里&#34;†,所以它被挑选。

您可以使用Assert.AreEqual<Amount>()Assert.AreEqual<Amount>()明确选择一个重载,但您可能最好将其中一个转化为&#34;缩小&#34;如果可能的话,必须是Assert.AreEqual<decimal>(),因为你的结构的这个特性会再次伤害你。 (华友世纪为单位测试发现缺陷)。

*另一个有效的重载选择是选择explicit,但它永远不会被推理选中,因为:

  1. 被拒绝的超载都被认为更好。
  2. 无论如何,它始终被非通用表单打败。
  3. 因此,只能通过在代码中包含Assert.AreEqual<object>来调用它。

    †编译器将对其说的所有内容都视为含义明显或完全不可理解。也有人这样。