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

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

在我的unit testing中,我断言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); } 

当我hoverAreEqual ,它显示第一个解析为

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

第二个导致

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

看起来int1隐式地转换为Amount ,而小数值1.5M则不是。

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

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

我有两个问题:

  1. 这种行为有什么解释?
  2. 如何修复Amount结构,以便两个测试都能成功?

(我知道我可以改变测试做一个明确的转换,但如果我不是绝对必须,我不会)

我的Amount结构(只是一个显示问题的最小实现)

 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; } } 

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

由于隐式转换,编译器能够选择Assert.AreEqual(1.5M, amount);Assert.AreEqual(1.5M, amount); 价值相等。*

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

由于通过推理没有重载选择,因此无法将其放入列表中以选择最佳匹配,并且只有(object, object)表单可用。 所以这是挑选的。

使用Assert.AreEqual(1, amount)然后由于存在从intAmount的隐式转换(通过隐式int-> decimal)但没有从Amountint的隐式转换,编译器认为“显然它们意味着Assert.AreEqual()这里“†,所以它被选中了。

您可以使用Assert.AreEqual()Assert.AreEqual()明确选择一个重载,但是如果可能的话,您可能最好使其中一个转换“明显缩小”因为你的结构的这个function会再次伤害你。 (华友世纪为单位测试发现缺陷)。


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

  1. 被拒绝的超载都被认为更好。
  2. 无论如何,它始终被非genericsforms所取代。

因此,只能通过在代码中包含来调用它。

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