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

6
假设我们有以下类型:
struct MyNullable<T> where T : struct
{
    T Value;

    public bool HasValue;

    public MyNullable(T value)
    {
        this.Value = value;
        this.HasValue = true;
    }

    public static implicit operator T(MyNullable<T> value)
    {
        return value.HasValue ? value.Value : default(T);
    }
}

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

MyNullable<int> i1 = new MyNullable<int>(1);
MyNullable<int> i2 = new MyNullable<int>(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<Money> m1 = 
   new MyNullable<Money>(new Money(10, CurrenciesCode.USD));
MyNullable<Money> m2 = 
   new MyNullable<Money>(new Money(20, CurrenciesCode.USD));

Money m3 = m1 + m2;

现在的问题是,为什么编译器会生成 "error CS0019: Operator '+' cannot be applied to operands of type 'MyNullable<Money>' and 'MyNullable<Money>'" 这个错误信息?

2个回答

10

这是一个有趣的问题……例如,使用 Decimal 是有效的,但使用 TimeSpan 就不行。它们都是合适的 .NET 类型(不像 float 等原始类型),并且都有一个加号运算符。很奇怪!

当然,你可以用以下方法强制转换:

Money m3 = (Money)m1 + (Money)m2;
并且如果你只是使用Nullable<T>,它就可以自由地工作 - 当然,你还会得到编译器和运行时的(装箱)支持。这里有没有使用Nullable<T>的理由呢?
我会查看规范;在此期间,您可能想将运算符升级为MyNullable<T>;对于常规的Nullable<T>,C#编译器提供了对该类型支持的“抬升”运算符,但是您无法自己完成。您能做的最好的事情就是提供所有明显的运算符,并希望该类型支持它;-p 要访问泛型运算符,请参见此处,可在此免费下载
请注意,您可能需要应用适当的“抬升”检查-即:
x + y => (x.HasValue && y.HasValue)
          ? new MyNullable<T>(x.Value + y.Value)
          : new MyNullable<T>();

更新

不同的处理方式似乎与14.7.4(ECMA 334 v4)“加法运算符”有关,其中对于包括decimal在内的一系列类型进行了预定义(所以这是我做的一个糟糕的测试),因为根据14.2.4(同样的)“二元运算符重载决策”,预定义的运算符确实获得了特殊提及。虽然我不完全理解它。


不幸的是,Nullable<Money> 也不起作用,我觉得内置的基元类型以某种方式得到了特殊处理,尽管我不知道为什么。我在 C# 规范中查找了用户定义的隐式转换,但恐怕我在技术术语中迷失了... 希望你能发现这很奇怪。 - Lasse V. Karlsen
@lassevk - Nullable<Money> 可以正常工作 - 只是 m1 + m2 的结果是 Nullable<Money>,而不是 Money。 - Marc Gravell
MyNullable在处理方面与其他方式不同。Nullable类型的值不能隐式地使用,而MyNullable正试图实现这一点。 - AnthonyWJones
"Money? m3 = m1 + m2;" - 编译并运行,结果如预期的30。 - Marc Gravell
嗯,好的,我一定是让代码出了问题,或者可能没读到错误信息。 - Lasse V. Karlsen
显示剩余4条评论

9

Marc的想法是对的 - 这在C# 3.0规范的7.2.4节中有所体现 - 二元运算符重载决议。

基本上,步骤如下:

  • 我们需要解析“X + Y”的实现,其中X和Y都是MyNullable<Money>
  • 查看第7.2.5节(候选用户定义的运算符),我们得到一个空集,因为MyNullable<T>没有重载+。
  • 回到7.2.4,候选运算符集是+的内置二元运算符集,即int+int、decimal+decimal等。
  • 然后应用7.4.3中的重载决议规则。当我们执行MyNullable<int> + MyNullable<int>时,这有效,因为每个参数都会隐式转换为int - 但当我们执行MyNullable<Money> + MyNullable<Money>时,它无效,因为Money + Money不在候选运算符集中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接