C#如何处理运算符重载

时间:2012-02-29 14:19:41

标签: c# optimization operator-overloading

假设我在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 个答案:

答案 0 :(得分:1)

单个表达式与单独的语句相同(就生成的代码而言),除了它有一个命名的局部变量。 JIT可以优化它,但编译器不能。请注意,如果Complex是值类型,那么它实际上不会涉及分配对象 1

值得注意的是,+运算符是专门为C#中的string处理的(它由语言指定;它不在框架本身中)正是为了避免这种情况:{{1是(或至少可以)转换为x + y + z以避免创建临时字符串。


1 “对象”我指的是一个带有对象头(类型引用,同步块等)的内存区域,后跟在堆上分配的字段,而不是“值类型”值“只包含数据本身,可能在堆或堆栈上。堆栈/堆部分当然是implementation detail,但是可能很重要......我相信它 可以合理地区分“只是一个值类型值”和“一个完整的”对象”。

答案 1 :(得分:1)

关于编译输出,您推断的逐步过程都不正确。 CLR是一个堆栈机器,由于一次性添加所有四个的代码并不表示需要单独分配中间总计作为命名变量,它会更新运行总计(或者更确切地说是弹出并推送顶部堆栈元素)。

两个逐步版本都需要相同数量的实际操作;一元加法版本只需要少一个局部变量分配。

下面是通过添加四个输入复数的三种不同方式产生的实际IL。请注意,在我的版本中,我将Complex类(我假设您从How to: Use Operator Overloading to Create a Complex Number Class (C# Programming Guide)取消)更改为保留两个double值,而不是两个int s。为简洁起见,我在C#和IL代码中省略了c1..c4的声明。简而言之,两个推断的逐步方法都需要两个额外的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