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

com*_*cme 7 c# struct unit-testing implicit-conversion

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

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

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

    Assert.AreEqual(1.5M, amount);
}
Run Code Online (Sandbox Code Playgroud)

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

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

    Assert.AreEqual(1, amount);
}
Run Code Online (Sandbox Code Playgroud)

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

public static void AreEqual(object expected, object actual);
Run Code Online (Sandbox Code Playgroud)

而第二个导致

public static void AreEqual<T>(T expected, T actual);
Run Code Online (Sandbox Code Playgroud)

看起来这个int1是隐式地转换为a Amount,而decimal1.5M则不是.

我不明白为什么会这样.我本来希望恰恰相反.第一次单元测试应该能够投射decimalAmount.

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

我有两个问题:

  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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*nna 6

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

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

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

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

随着Assert.AreEqual(1, amount)然后因为是从隐式转换intAmount(通过隐含的内部- >十进制),但没有从隐式转换Amountint编译器认为"很明显,他们的意思是Assert.AreEqual<Amount>()在这里"†,并因此被采摘.

你可以明确地挑过载使用Assert.AreEqual<Amount>()Assert.AreEqual<decimal>(),但你可能会更好过让你转换的"缩小"从有到是一个explicit,如果在所有可能的,因为你的结构的这一特征会伤害你.(华友世纪为单位测试发现缺陷).


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

  1. 被拒绝的超载都被认为更好.
  2. object无论如何,它总是被非泛型形式打败.

因此,只能通过<object>在代码中包含它来调用它.

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