【问题标题】:c# Addition operator borrowed from implicit conversion to and from some types, but not others?c#加法运算符从隐式转换中借用到某些类型,而不是其他类型?
【发布时间】:2019-11-02 03:14:57
【问题描述】:

让我们创建一个类并使其隐式转换为int

class Foo
{
    int val;
    public Foo() { }
    public Foo(int val) => this.val = val;
    public static implicit operator int(Foo f) => f.val;
    public static implicit operator Foo(int val) => new Foo(val);
}

以下代码编译正常:

Foo foo1 = new Foo();
Foo foo2 = new Foo();
Foo sum = foo1 + foo2;

它只是将每个值转换为int,执行加法,然后转换回Foo

现在让我们尝试使用decimal 而不是int

class Foo
{
    decimal val;
    public Foo() { }
    public Foo(decimal val) => this.val = val;
    public static implicit operator decimal(Foo f) => f.val;
    public static implicit operator Foo(decimal val) => new Foo(val);
}

再次,代码:

Foo sum = foo1 + foo2;

工作正常。

现在,让我们尝试另一个定义了加法运算符的结构,TimeSpan

class Foo
{
    TimeSpan val;
    public Foo() { }
    public Foo(TimeSpan val) => this.val = val;
    public static implicit operator TimeSpan(Foo f) => f.val;
    public static implicit operator Foo(TimeSpan val) => new Foo(val);
}

现在,当我们尝试时

Foo sum = foo1 + foo2;

我们得到一个编译器错误:

错误 CS0019 运算符“+”不能应用于“Foo”和“Foo”类型的操作数

这看起来很奇怪。 decimalTimeSpan 都定义了重载的 + 运算符:

(来自元数据)

public static TimeSpan operator +(TimeSpan t1, TimeSpan t2);


public static Decimal operator +(Decimal d1, Decimal d2);

为什么这适用于某些类型而不适用于其他类型?

【问题讨论】:

    标签: c# operator-overloading


    【解决方案1】:

    使用这个:

    // int
    Foo sum = foo1 + foo2;
    

    还有:

    // decimal
    Foo sum = foo1 + foo2;
    

    还有:

    // TimeSpan
    Foo sum = (TimeSpan)foo1 + (TimeSpan)foo2;
    

    还有:

    // TimeSpan
    var sum = (TimeSpan)foo1 + foo2;
    

    这是第一个的 IL 代码:

    // ...
    // Foo foo = (int)f + (int)f2;
    IL_000d: ldloc.0
    IL_000e: call int32 ConsoleApp.Foo::op_Implicit(class ConsoleApp.Foo)
    IL_0013: ldloc.1
    IL_0014: call int32 ConsoleApp.Foo::op_Implicit(class ConsoleApp.Foo)
    IL_0019: add
    IL_001a: call class ConsoleApp.Foo ConsoleApp.Foo::op_Implicit(int32)
    IL_001f: stloc.2
    

    第二个:

    //...
    // decimal num = (decimal)f + (decimal)f2;
    IL_000d: ldloc.0
    IL_000e: call valuetype [mscorlib]System.Decimal ConsoleApp.Foo::op_Implicit(class ConsoleApp.Foo)
    IL_0013: ldloc.1
    IL_0014: call valuetype [mscorlib]System.Decimal ConsoleApp.Foo::op_Implicit(class ConsoleApp.Foo)
    IL_0019: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Addition(valuetype [mscorlib]System.Decimal, valuetype [mscorlib]System.Decimal)
    IL_001e: stloc.2
    //...
    

    第三个:

    // ...
    // TimeSpan timeSpan = (TimeSpan)f + (TimeSpan)f2;
    IL_000d: ldloc.0
    IL_000e: call valuetype [mscorlib]System.TimeSpan ConsoleApp.Foo::op_Implicit(class ConsoleApp.Foo)
    IL_0013: ldloc.1
    IL_0014: call valuetype [mscorlib]System.TimeSpan ConsoleApp.Foo::op_Implicit(class ConsoleApp.Foo)
    IL_0019: call valuetype [mscorlib]System.TimeSpan [mscorlib]System.TimeSpan::op_Addition(valuetype [mscorlib]System.TimeSpan, valuetype [mscorlib]System.TimeSpan)
    IL_001e: stloc.2
    // ...
    

    第四个:

    //...
    // TimeSpan timeSpan = (TimeSpan)f + (TimeSpan)f2;
    IL_000d: ldloc.0
    IL_000e: call valuetype [mscorlib]System.TimeSpan ConsoleApp.Foo::op_Implicit(class ConsoleApp.Foo)
    IL_0013: ldloc.1
    IL_0014: call valuetype [mscorlib]System.TimeSpan ConsoleApp.Foo::op_Implicit(class ConsoleApp.Foo)
    IL_0019: call valuetype [mscorlib]System.TimeSpan [mscorlib]System.TimeSpan::op_Addition(valuetype [mscorlib]System.TimeSpan, valuetype [mscorlib]System.TimeSpan)
    IL_001e: stloc.2
    //...
    

    在第一种情况下,代码调用 int32 op_Implicit 接下来将两个整数与标准的 add 汇编记忆指令相加,该指令将压入堆栈的最后两个值相加并将结果压入其中。

    在其他情况下,方法::op_Addition 被调用,它使用最后两个压入堆栈的值作为参数将结果压入堆栈。

    在第三种情况下,如果我们转换加法的第一个操作数,编译器会推断转换为预期结果类型的第二个操作数的类型。

    使用decimalTimeSpan 并没有真正的区别。

    因此,当使用数字以外的类型时,如果不将两个 Foo 之一转换为预期类型,编译器就无法推断用于加法的类型,因此它会尝试添加两个 Foo 而不推断预计。

    现在,如果我们看一下元数据提供的方法表的DecimalTimeSpan 类接口,我们会看到Decimal 实现了IConvertible,但没有实现TimeSpan

    IConvertibleToInt32ToDecimal 方法。

    如果我们尝试 Foo/DateTime,我们会遇到与 TimeSpan 相同的问题,但 DateTime 被标记为实现 Iconvertible... 但是类定义没有转换方法...没有实现,正如我们在 Microsoft Docs 中看到的那样:“不支持此转换”。

    也许这可以解释为什么编译器可以推断 int 和 decimal 的类型来进行加法,但不能推断 TimeSpan 和 DateTime。也许对于默认的+ 运算符,编译器仅搜索可转换类型的隐式运算符。这里只是一个可能是假的想法,我不知道。

    如果我们将这个重载的运算符添加到 Foo/TimeSpan:

    public static Foo operator +(Foo value1, TimeSpan value2)
    {
      return (TimeSpan)value1 + value2;
    }
    

    这会编译并在 foo2 上的 operator + 旁边调用 operator TimeSpan

    TimeSpan sum = foo1 + foo2;
    

    因为这里编译器看到有+ 重载运算符并生成此IL 代码:

    // TimeSpan timeSpan = v + f;
    IL_001d: ldloc.0
    IL_001e: ldloc.1
    IL_001f: call valuetype [mscorlib]System.TimeSpan ConsoleApp.Foo::op_Implicit(class ConsoleApp.Foo)
    IL_0024: call class ConsoleApp.Foo ConsoleApp.Foo::op_Addition(class ConsoleApp.Foo, valuetype [mscorlib]System.TimeSpan)
    IL_0029: call valuetype [mscorlib]System.TimeSpan ConsoleApp.Foo::op_Implicit(class ConsoleApp.Foo)
    IL_002e: stloc.2
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-14
      • 1970-01-01
      相关资源
      最近更新 更多