c# - c#加法运算符从隐式转换中借用到某些类型,而不是其他类型?
问题描述
让我们创建一个类并使其隐式转换为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”类型的操作数
这似乎很奇怪。decimal
和都TimeSpan
定义了重载的 + 运算符:
(来自元数据)
public static TimeSpan operator +(TimeSpan t1, TimeSpan t2);
public static Decimal operator +(Decimal d1, Decimal d2);
为什么这适用于某些类型而不适用于其他类型?
解决方案
使用这个:
// 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
next 将两个整数与标准add
汇编记忆指令相加,该指令将最后两个压入堆栈的值相加并将结果压入堆栈。
在其他情况下::op_Addition
,调用方法并使用最后两个压入堆栈的值作为参数将结果压入堆栈。
在第三种情况下,如果我们转换加法的第一个操作数,编译器会推断转换为预期结果类型的第二个操作数的类型。
decimal
使用or之间没有真正的区别TimeSpan
。
因此,似乎在使用数字以外的类型时,如果不将两个 Foo 之一转换为预期类型,编译器就无法推断用于加法的类型,因此它会尝试添加两个 Foo 而不会推断出预期的类型。
现在,如果我们看一下元数据提供的方法表Decimal
的TimeSpan
类接口,我们会看到Decimal
实现IConvertible
但不是TimeSpan
。
IConvertible
有ToInt32
和ToDecimal
方法。
如果我们尝试 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;
}
这会编译并operator TimeSpan
在 foo2 旁边调用operator +
:
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
推荐阅读
- python - Pandas - 如果少于 N 则删除唯一行
- spring - 如何将 tx:annotation-driven 添加到 grails
- android - Android 应用程序输入数字输入刻度符号
- javascript - 有没有更好的方法来使用随机的开始和结束动画来制作复杂的动画?
- java - Project Lombok 是否与使用 getter 和 setter 的数据封装相矛盾?
- r - while循环内的矩阵条件
- firebase - Flutter 仅在 Firestore 的数组中创建 documentID 与 String 匹配的项目
- html - 由于高度,页脚位置错误:100%
- java - 如何在这个程序中正确读取和存储输入?
- sql - 如何连接来自同一个表的两列并在 SQL 中格式化新列