我已经创建了一个自定义结构来表示金额。它基本上是decimal
周围的包装器。它具有一个隐式转换操作员,将其放回decimal
。
在我的单位测试中,我断言该金额等于原始小数值,但测试失败。
[TestMethod]
public void AmountAndDecimal_AreEqual()
{
Amount amount = 1.5M;
Assert.AreEqual(1.5M, amount);
}
当我使用int时(为此我没有创建转换操作员),test do 成功。
[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
不是。
我不明白为什么会发生这种情况。我会期望相反。第一个单元测试应能够将decimal
施放到Amount
。
当我向int
添加隐式铸件(这是没有意义的)时,第二个单元测试也会失败。因此,添加隐式铸造操作员打破了单位测试。
我有两个问题:
- 这种行为的解释是什么?
- 如何修复
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
ly时,就会发生坏事,这是一个例子。
由于具有隐式转换,因此编译器能够以相等值选择Assert.AreEqual<decimal>(1.5M, amount);
和Assert.AreEqual<Amount>(1.5M, amount);
。*
由于它们相等,因此都不会通过推理来挑选超载。
由于没有推断要选择的过载,因此都不会将其纳入列表,以选择最佳匹配,只有(object, object)
表单可用。所以这是挑选的。
使用Assert.AreEqual(1, amount)
,然后是因为从int
到Amount
有隐式转换(通过Intionit Int-> Decimal),但是从Amount
到int
CC_21没有任何隐含的转换,这显然是在此处,显然它们是在这里的Assert.AreEqual<Amount>()
,因此选择了它。。
您可以明确选择使用Assert.AreEqual<Amount>()
或Assert.AreEqual<decimal>()
的过载,但是如果可能的话,您最好将其中一种转换为"缩小",因为您的结构的此功能会再次伤害您。(用于单位测试发现缺陷的呼吸)。
*另一个有效的超载选择是选择Assert.AreEqual<object>
,但从未通过推理选择,因为:
- 两个被拒绝的超载都被认为更好。
- 无论如何,它总是被采用
object
的非生成形式击败。
因此,只能通过在代码中加入<object>
来调用。
†编译器将其视为明显的意义或完全难以理解的一切。也有这样的人。