Используя это:
// 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