为什么C#编译器不调用隐式转换运算符?

假设我们有以下类型:

struct MyNullable where T : struct { T Value; public bool HasValue; public MyNullable(T value) { this.Value = value; this.HasValue = true; } public static implicit operator T(MyNullable value) { return value.HasValue ? value.Value : default(T); } } 

并尝试编译以下代码片段:

 MyNullable i1 = new MyNullable(1); MyNullable i2 = new MyNullable(2); int i = i1 + i2; 

这剪得很好,没有错误。 i1和i2转换为整数和加法评估。

但如果我们有以下类型:

 struct Money { double Amount; CurrencyCodes Currency; /*enum CurrencyCode { ... } */ public Money(double amount, CurrencyCodes currency) { Amount = amount; Currency = currency; } public static Money operator + (Money x, Money y) { if (x.Currency != y.Currency) // Suppose we implemented method ConvertTo y = y.ConvertTo(x.Currency); return new Money(x.Amount + y.Amount, x.Currency); } } 

尝试编译另一个代码段:

 MyNullable m1 = new MyNullable(new Money(10, CurrenciesCode.USD)); MyNullable m2 = new MyNullable(new Money(20, CurrenciesCode.USD)); Money m3 = m1 + m2; 

现在问题是,为什么编译器生成“ 错误CS0019:运算符’+’不能应用于’MyNullable ‘和’MyNullable ‘类型的操作数 ?”

Marc在右边 – 它是C#3.0规范中的第7.2.4节 – 二元运算符重载分辨率。

基本上步骤是:

  • 我们需要解决“X + Y”的实现,其中X和Y都是MyNullable
  • 查看7.2.5节(候选用户定义的运算符),我们最终得到一个空集,因为MyNullable不会重载+。
  • 回到7.2.4,候选运算符集是+的内置二元运算符集,即int + int,十进制+十进制等。
  • 然后应用7.4.3中的过载分辨率规则。 当我们正在执行MyNullable + MyNullable这是有效的,因为每个参数都隐式转换为int – 但是当我们正在执行MyNullable + MyNullable不起作用,因为Money + Money不在候选运营商的集合中。

这是一个有趣的问题……例如,它适用于Decimal ,但不适用于TimeSpan ,它们都是正确的.NET类型(不像float等,它们是灵长类)并且都有+运算符。 好奇!

当然,你可以扭动arm:

 Money m3 = (Money)m1 + (Money)m2; 

而你只需使用Nullable它就可以免费工作 – 当然 – 再加上你得到编译器+运行时(拳击)支持。 有没有理由不在这里使用Nullable

我会看看规格; 在此期间,您可能会考虑将运营商推广到MyNullable ; 使用常规Nullable ,C#编译器为类型支持的那些提供“提升”运算符,但是你不能自己做。 您可以做的最好的是提供所有明显的,并希望类型支持它;-p要访问具有generics的运算符,请参阅此处 ,可在此处免费下载。

请注意,您可能希望应用适当的“提升”检查 – 即

 x + y => (x.HasValue && y.HasValue) ? new MyNullable(x.Value + y.Value) : new MyNullable(); 

更新

不同的处理看起来与14.7.4(ECMA 334 v4)“加法算子”有关,其中它是为包括小数在内的一系列类型预先定义的(因此这是我的一个不好的测试),因为14.2.4(相同)“二元运算符重载分辨率”,预定义运算符确实得到特别提及。 不过,我并没有声称完全理解它。