我创建了一个表示金额的自定义结构。它基本上是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);
看起来int
值1
隐含地投射到Amount
,而decimal
值1.5M
则不是。{/ p>
我不明白为什么会这样。我本来希望恰恰相反。第一个单元测试应该能够将decimal
转换为Amount
。
当我向int
添加隐式强制转换(这没有意义)时,第二个单元测试也会失败。因此,添加隐式强制转换运算符会破坏单元测试。
我有两个问题:
Amount
结构,以便两个测试都成功?(我知道我可以更改测试以进行显式转换,但如果我不是绝对必须,我不会)
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;
}
}
答案 0 :(得分:6)
当您可以在两个方向转换implicit
时,会发生不好的事情,这就是一个例子。
由于隐式转换,编译器能够以相同的值选择Assert.AreEqual<decimal>(1.5M, amount);
和Assert.AreEqual<Amount>(1.5M, amount);
。*
由于它们相等,所以推理都不会引起过载。
由于推理选择没有超载,因此不会将其列入选择最佳匹配的列表中,只有(object, object)
表单可用。所以这是被选中的人。
使用Assert.AreEqual(1, amount)
,因为存在从int
到Amount
的隐式转换(通过隐式int-&gt;十进制),但没有从Amount
隐式转换为{{ 1}}编译器认为&#34;显然它们意味着int
在这里&#34;†,所以它被挑选。
您可以使用Assert.AreEqual<Amount>()
或Assert.AreEqual<Amount>()
明确选择一个重载,但您可能最好将其中一个转化为&#34;缩小&#34;如果可能的话,必须是Assert.AreEqual<decimal>()
,因为你的结构的这个特性会再次伤害你。 (华友世纪为单位测试发现缺陷)。
*另一个有效的重载选择是选择explicit
,但它永远不会被推理选中,因为:
因此,只能通过在代码中包含Assert.AreEqual<object>
来调用它。
†编译器将对其说的所有内容都视为含义明显或完全不可理解。也有人这样。