c#加法运算符是从某些类型的隐式转换中借用来的,而不是从其他类型借用来的?

c#加法运算符是从某些类型的隐式转换中借用来的,而不是从其他类型借用来的?,c#,operator-overloading,C#,Operator Overloading,让我们创建一个类,并使其在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); }

让我们创建一个类,并使其在
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);
}
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 foo1 = new Foo();
Foo foo2 = new Foo();
Foo sum = foo1 + foo2;
它只是将每个值转换为
int
,执行加法,然后再转换回
Foo

现在,让我们尝试使用
decimal
而不是
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);
}
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”类型的操作数

这似乎很奇怪。
decimal
TimeSpan
都定义了重载+运算符:

(来自元数据)

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

使用以下方法:

// int
Foo sum = 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
assembly memonic指令添加两个整数,该指令将堆栈中最后两个推送的值相加,并将结果推送到堆栈中

在其他情况下,调用方法
::op_Addition
,并使用堆栈中推送的最后两个值作为参数,将结果推送到堆栈中

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

使用
decimal
TimeSpan
之间没有真正的区别

因此,似乎在使用数字以外的类型时,如果不将两个Foo中的一个强制转换为预期类型,编译器就无法推断用于加法的类型,因此它尝试添加两个Foo而不推断为预期类型

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

IConvertible
具有
ToInt32
ToDecimal
方法

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

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

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

public static Foo operator +(Foo value1, TimeSpan value2)
{
  return (TimeSpan)value1 + value2;
}
这将编译并在foo2上调用
操作符+
旁边的
操作符时间跨度

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
TimeSpan sum = foo1 + foo2;
// 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