使用ref节省内存传递引用类型吗?

在C#中,方法的参数可以是引用类型或值类型。 传递引用类型时,将传递引用的副本。 这样,如果在方法内部我们尝试将传递的引用重新分配给另一个对象实例,则在该方法之外,重新分配是不可见的。

为了使其工作,C#具有ref修饰符。 使用ref传递引用类型实际上使用原始引用而不是副本。 (如我错了请纠正我)。

在这种情况下,由于我们没有创建引用的副本,我们是否保存了任何内存? 如果广泛调用方法,这是否会提高应用程序的整体性能?

谢谢!

要求

不,它没有。 如果有的话,由于额外的查找,它会变慢。

除非您特意打算稍后分配,否则没有理由通过引用传递引用类型。


certificate

由于有些人似乎认为编译器传递“变量本身 ”,请查看此代码的反汇编:

 using System; static class Program { static void Test(ref object o) { GC.KeepAlive(o); } static void Main(string[] args) { object temp = args; Test(ref temp); } } 

这是(在x86上,为简单起见):

 // Main(): // Set up the stack 00000000 push ebp // Save the base pointer 00000001 mov ebp,esp // Set up stack pointer 00000003 sub esp,8 // Reserve space for local variables 00000006 xor eax,eax // Zero out the EAX register // Copy the object reference to the local variable `temp` (I /think/) 00000008 mov dword ptr [ebp-4],eax // Copy its content to memory (temp) 0000000b mov dword ptr [ebp-8],ecx // Copy ECX (where'd it come from??) 0000000e cmp dword ptr ds:[00318D5Ch],0 // Compare this against zero 00000015 je 0000001C // Jump if it was null (?) 00000017 call 6F910029 // (Calls some internal method, idk) // THIS is where our code finally starts running 0000001c mov eax,dword ptr [ebp-8] // Copy the reference to register 0000001f mov dword ptr [ebp-4],eax // ** COPY it AGAIN to memory 00000022 lea ecx,[ebp-4] // ** Take the ADDRESS of the copy 00000025 call dword ptr ds:[00319734h] // Call the method // We're done with the call 0000002b nop // Do nothing (breakpoint helper) 0000002c mov esp,ebp // Restore stack 0000002e pop ebp // Epilogue 0000002f ret // Return 

这是来自优化的代码编译。 显然,有一个变量的地址被传递,而不是“变量本身”。

是的,有一个原因:如果你想重新分配价值。 在这方面,值类型和引用类型没有区别。

请参阅以下示例:

 class A { public int B {get;set;} } void ReassignA(A a) { Console.WriteLine(aB); a = new A {B = 2}; Console.WriteLine(aB); } // ... A a = new A { B = 1 }; ReassignA(a); Console.WriteLine(aB); 

这将输出:

 1 2 1 

然而,性能与它无关。 这将是真正的微观优化。

Mehrdad的例子(两个版本)的分析视图

我会尝试更深入地了解Mehrdad的一个很好的certificate,对于像我这样不太好读取汇编代码的人。 当我们进行debbuging,单击Debug – > Windows – > Dissasembly时,可以在Visual Studio中捕获此代码。

使用REF的版本

源代码:

  namespace RefTest { class Program { static void Test(ref object o) { GC.KeepAlive(o); } static void Main(string[] args) { object temp = args; Test(ref temp); } } } 

汇编语言(x86)(仅显示不同的部分):

  object temp = args; 00000030 mov eax,dword ptr [ebp-3Ch] 00000033 mov dword ptr [ebp-40h],eax Test(ref temp); 00000036 lea ecx,[ebp-40h] //loads temp address's address on ecx? 00000039 call FD30B000 0000003e nop } 

版本没有REF

源代码:

  namespace RefTest { class Program { static void Test(object o) { GC.KeepAlive(o); } static void Main(string[] args) { object temp = args; Test(temp); } } } 

汇编语言(x86)(仅显示不同的部分):

  object temp = args; 00000035 mov eax,dword ptr [ebp-3Ch] 00000038 mov dword ptr [ebp-40h],eax Test(temp); 0000003b mov ecx,dword ptr [ebp-40h] //move temp address to ecx? 0000003e call FD30B000 00000043 nop } 

除了注释行之外,两个版本的代码都是相同的:使用ref,对函数的调用前面是LEA指令,没有参考,我们有一个更简单的MOV指令。 在执行该行之后,LEA已经为ecx寄存器加载了一个指向该对象的指针,而MOV已经为ecx加载了一个指向该对象的指针。 这意味着在第一种情况下FD30B000子程序(指向我们的测试function)将必须对内存进行额外访问才能到达对象。 如果我们检查这个函数的每个生成版本的汇编代码,我们可以看到在某些时候(实际上是两个版本之间唯一不同的行),进行了额外的访问:

 static void Test(ref object o) { GC.KeepAlive(o); } ... 00000025 mov eax,dword ptr [ebp-3Ch] 00000028 mov ecx,dword ptr [eax] ... 

虽然没有ref的函数可以直接到对象:

 static void Test(object o) { GC.KeepAlive(o); } ... 00000025 mov ecx,dword ptr [ebp-3Ch] ... 

希望它有所帮助。

按值传递引用类型不会复制对象。 它只创建对现有对象的新引用。 所以你不应该通过引用传递它,除非你真的需要。