Используя это:
// 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
, который использует последние два значения, помещенные в стек, в качестве параметров для помещения результата в стек.
В третьем случае, если мы приводим первый операнд добавления, компилятор выводит тип второго операнда, который приводится к типу ожидаемого результата.
Нет никакой реальной разницы между использованием 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;
}
Это компилируется и вызывает 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
02.11.2019