Сохраняет ли передача ссылочных типов с помощью ref память? - PullRequest
7 голосов
/ 24 августа 2011

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

Чтобы это работало, в C # есть модификатор ref. Передача ссылочного типа с ref фактически использует исходную ссылку вместо копии. (Поправь меня, если я ошибаюсь).

В этом случае, поскольку мы не создаем копию ссылки, мы сохраняем какую-либо память? Если метод часто вызывается, это улучшает общую производительность приложения?

Спасибо!

Ответы [ 4 ]

16 голосов
/ 24 августа 2011

претензии

Нет, это не так. Во всяком случае, это медленнее из-за дополнительного поиска.

Существует без причины передавать ссылочный тип по ссылке, если только вы не собираетесь специально назначить его позже.


Доказательство

Поскольку некоторые люди считают, что компилятор передает "саму переменную ", взгляните на разборку этого кода:

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

Это было из оптимизированной компиляции кода. Ясно, что есть адрес передаваемой переменной, а не «сама переменная».

5 голосов
/ 25 августа 2011

ОТКРЫТОЕ ПОСМОТРЕТЬ ПРИМЕР МЕРДАДА (ОБА ВЕРСИИ)

Я попытаюсь покопаться немного глубже на милых доказательствах Мехрдада, для тех, кто, как я, не очень хорошо читает ассемблерный код. Этот код может быть записан в Visual Studio, когда мы отлаживаем, нажимая Отладка -> Windows -> Сборка.

Версия с использованием 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]
...

Пока функция без ссылки может идти прямо к объекту:

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

Надеюсь, это помогло.

5 голосов
/ 24 августа 2011

Да, есть причина: если вы хотите переназначить значение. В этом отношении нет никакой разницы в типах значений и ссылочных типах.

См. Следующий пример:

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

Производительность, однако, не имеет к этому никакого отношения. Это была бы настоящая микрооптимизация.

0 голосов
/ 24 августа 2011

Передача ссылочного типа по значению не копирует объект. Это только создает новую ссылку на существующий объект. Таким образом, вы не должны передавать его по ссылке, если вам действительно не нужно.

...