Как C # справляется с перегрузкой операторов - PullRequest
3 голосов
/ 29 февраля 2012

Скажем, у меня есть класс комплексных чисел в C #, оператор сложения которого определяется следующим образом.

public static Complex operator +(Complex c1, Complex c2) 
 {
      return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);
 }

и используется вот так.

 Complex c1, c2, c3, c4;

 c1 = new Complex(...)
 ...
 c4 = new Complex(...)

 Complex csum = c1 + c2 + c3 + c4;

Теперь, мой вопрос, как компилятор C # + будет справляться с этим. Видимо, похоже, что он будет делать что-то вроде этого.

 ct1 = c1 + c2; // ct1 is a temporary object created by the compiler
 ct2 = ct1 + c3;
 csum = ct2 + c4;

Или он достаточно умен, чтобы понять, что может делать это лучше (меньше создавать новые временные объекты), как это.

 ct = c1 + c2;
 ct += c3;
 csum = ct + c4; 

Ответы [ 2 ]

1 голос
/ 29 февраля 2012

Ни одна из ваших выведенных пошаговых процедур не верна в отношении скомпилированного вывода. CLR является машиной стека, и поскольку код для добавления всех четырех сразу не указывает на необходимость отдельного выделения промежуточных итогов в качестве именованных переменных, он обновляет промежуточную сумму на месте (или, скорее, выдает и помещает верхний элемент стека).

Обе ваши пошаговые версии требуют одинакового количества реальных операций; версия с унарным сложением просто требует на одну локальную переменную меньше.

Ниже приведен фактический IL, полученный тремя различными способами сложения четырех входных комплексных чисел. Обратите внимание, что в моей версии я изменил класс Complex (который, как я полагаю, вы подняли с Как: использовать перегрузку оператора для создания класса комплексных чисел (Руководство по программированию в C #) ), чтобы хранить два значения double вместо двух int s. Я пропустил объявление c1..c4 в коде C # и IL для краткости. Короче говоря, оба выведенных пошаговых подхода требуют по два дополнительных вызова stloc.s (отправка значения в список переменных) и ldloc.s (получение значения из списка переменных) каждый.

Оригинал: Добавить все четыре в ряд:

C #:

Complex csum = c1 + c2 + c3 + c4;

IL:

// MultipleAdd : 5 Locals, maxstack 3
IL_0068:  nop
IL_0069:  ldloc.0
IL_006a:  ldloc.1
IL_006b:  call       valuetype Test.Complex Test.Complex::op_Addition(valuetype Test.Complex,
                                                                    valuetype Test.Complex)
IL_0070:  ldloc.2
IL_0071:  call       valuetype Test.Complex Test.Complex::op_Addition(valuetype Test.Complex,
                                                                    valuetype Test.Complex)
IL_0076:  ldloc.3
IL_0077:  call       valuetype Test.Complex Test.Complex::op_Addition(valuetype Test.Complex,
                                                                    valuetype Test.Complex)
IL_007c:  stloc.s    csum
IL_007e:  ret

Вывод 1: три двоичных дополнения

C #:

Complex ct1 = c1 + c2; 
Complex ct2 = ct1 + c3;  
Complex csum = ct2 + c4; 

IL:

// BinaryAdd : 7 Locals, maxstack 3
IL_0068:  nop
IL_0069:  ldloc.0
IL_006a:  ldloc.1
IL_006b:  call       valuetype Test.Complex Test.Complex::op_Addition(valuetype Test.Complex,
                                                                    valuetype Test.Complex)
IL_0070:  stloc.s    ct1
IL_0072:  ldloc.s    ct1
IL_0074:  ldloc.2
IL_0075:  call       valuetype Test.Complex Test.Complex::op_Addition(valuetype Test.Complex,
                                                                    valuetype Test.Complex)
IL_007a:  stloc.s    ct2
IL_007c:  ldloc.s    ct2
IL_007e:  ldloc.3
IL_007f:  call       valuetype Test.Complex Test.Complex::op_Addition(valuetype Test.Complex,
                                                                    valuetype Test.Complex)
IL_0084:  stloc.s    csum
IL_0086:  ret 

Вывод 2: Унарные дополнения

C #:

Complex ct = c1 + c2; 
ct += c3; 
Complex csum = ct + c4;

IL:

// UnaryAdd : 6 Locals, maxstack 3
IL_0068:  nop
IL_0069:  ldloc.0
IL_006a:  ldloc.1
IL_006b:  call       valuetype Test.Complex Test.Complex::op_Addition(valuetype Test.Complex,
                                                                    valuetype Test.Complex)
IL_0070:  stloc.s    ct
IL_0072:  ldloc.s    ct
IL_0074:  ldloc.2
IL_0075:  call       valuetype Test.Complex Test.Complex::op_Addition(valuetype Test.Complex,
                                                                    valuetype Test.Complex)
IL_007a:  stloc.s    ct
IL_007c:  ldloc.s    ct
IL_007e:  ldloc.3
IL_007f:  call       valuetype Test.Complex Test.Complex::op_Addition(valuetype Test.Complex,
                                                                    valuetype Test.Complex)
IL_0084:  stloc.s    csum
IL_0086:  ret
1 голос
/ 29 февраля 2012

Одно выражение эквивалентно (в терминах сгенерированного кода) отдельным операторам, за исключением того, что для него существует именованная локальная переменная. JIT может оптимизировать это, но компилятор не может. Обратите внимание, что если Complex является типом значения, то в действительности это не будет связано с выделением объектов 1 .

Стоит отметить, что оператор + обрабатывается специально для string в C # (это определяется языком; его нет в самой платформе) именно для того, чтобы избежать такого рода вещей: x + y + z есть (или в как минимум можно) преобразовать в string.Concat(x, y, z), чтобы избежать создания временных строк.


1 Под «объектом» я подразумеваю область памяти с заголовком объекта (указатель типа, блок синхронизации и т. Д.), За которым следуют поля, выделенные в куче, а не «значение типа значения "который включает в себя только сами данные и может находиться в куче или стеке. Часть стека / кучи - это, конечно, деталь реализации , но потенциально важная ... и я считаю, что разумно различать между "просто значением типа значения" и " полный объект ".

...