Сохраняет ли передача ссылочных типов с помощью ref память?
в C# параметры метода могут быть ссылочными типами или типами значений. При передаче ссылочных типов, передается копия ссылки. Таким образом, если внутри метода мы пытаемся переназначить переданную ссылку на другой экземпляр объекта, вне метода переназначение не отображается.
чтобы это работало, C# имеет модификатор ref. Передача ссылочного типа с ref фактически использует исходную ссылку вместо копии. (Поправьте меня, если я неправильный.)
в этом случае, поскольку мы не создаем копию ссылки, мы сохраняем какую-либо память? Если метод широко вызывается, улучшает ли это общую производительность приложения?
спасибо!
4 ответов
претензии
нет, это не так. Во всяком случае, это медленнее из-за дополнительного поиска.
здесь незачем передать ссылочный тип ПО ССЫЛКЕ, Если вы специально не собираетесь назначить ему позже.
доказательство
поскольку некоторые люди, похоже, думают, что компилятор передает " переменную ", взгляните на разборку этого кода:
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(a.B);
a = new A {B = 2};
Console.WriteLine(a.B);
}
// ...
A a = new A { B = 1 };
ReassignA(a);
Console.WriteLine(a.B);
это будет выход:
1
2
1
производительность, однако, не имеет к этому никакого отношения. Это будет настоящая микро-оптимизация.
представление DISSASEMBLER примера Mehrdad (обе версии)
я попытаюсь копнуть немного глубже на хорошем доказательстве Мехрдада, для таких, как я, которые не очень хорошо читают сборочный код. Этот код можно захватить в Visual Studio, когда мы debbuging, нажав Debug - > Windows - > Dissasembly.
ВЕРСИЯ С ИСПОЛЬЗОВАНИЕМ 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, без ref у нас есть более простая инструкция MOV. После выполнения этой строки, LEA загрузила регистр ecx с указателем на указатель на объект, тогда как MOV загрузил ecx с указателем на объект. Это означает, что подпрограмме FD30B000 (указывающей на нашу тестовую функцию) в первом случае придется сделать дополнительный доступ к памяти, чтобы добраться до объекта. Если мы проверим код сборки для каждой производимой версии этой функции, мы увидим, что в какой-то момент (фактически единственная строка, которая отличается между двумя версиями) дополнительный доступ сделано:
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]
...
надеюсь, что это помогло.
передача ссылочного типа по значению не копирует объект. Он создает только новую ссылку на существующий объект. Поэтому вы не должны передавать его по ссылке, Если вам действительно не нужно.