Почему параметр ссылочного типа неявно передается функции по ссылке в C#? - PullRequest
0 голосов
/ 26 апреля 2020

Итак, я изучал алгоритмы Heapsort и прекрасно это понимал. Однако, глядя на мой код, я на самом деле не понимаю, почему он работает, поскольку в C#, если я хочу изменить значение параметра, передав его в функцию, я должен использовать ключевое слово ref, в противном случае это значение - Параметр типа, который не должен изменяться при передаче в функцию.

public static class HeapSort
{
    public static List<int> Sort(List<int> array)
    {
        List<int> result = new List<int>();
        int n = array.Count;

        for(int i = n/2 - 1; i >= 0; i--)
        {
            Heapify(array, n, i);
        }

        int sizeOfArray = n;
        while(result.Count != n)
        {
            result.Insert(0, array[0]);
            array[0] = array[sizeOfArray - 1];
            array.RemoveAt(sizeOfArray - 1);
            sizeOfArray--;
            Heapify(array, sizeOfArray, 0);
        }

        return result;
    }

    private static void Heapify(List<int> array, int n, int i)
    {
        int smallest = i;
        int left = 2 * i + 1;
        int right = 2 * i + 2;

        if(left < n && array[smallest] > array[left])
        {
            smallest = left;
        }

        if(right < n && array[smallest] > array[right])
        {
            smallest = right;
        }

        if(smallest != i)
        {
            int temp = array[smallest];
            array[smallest] = array[i];
            array[i] = temp;
            Heapify(array, n, smallest);
        }

    }
}

Я не понимаю, почему вызов Heapify без ref работает при изменении массива.

1 Ответ

1 голос
/ 26 апреля 2020

В c# есть ссылка типы и значение типы. Типы значений передаются по значению, если вы не передаете их с ключевым словом ref. Ссылочные типы всегда передаются по ссылке.

Например, если вы объявляете две переменные, подобные этой

List<int> myList = new List<int>();
int myint = 0;

myInt содержит значение 0, но myList содержит только ссылку, которая указывает на фактический экземпляр List<int>. Это означает, что когда вы передаете myInt методу, вы просто передаете копию его значения. Когда вы передаете myList, вы делаете то же самое - вы передаете копию его значения, но его значение является ссылкой на экземпляр List<int>, поэтому копия этой ссылки по-прежнему ссылается на тот же List<int> в куче.

Когда вы передаете переменную с ключевым словом ref, вы фактически передаете указатель на исходную переменную. Если вы передадите myInt с ключевым словом ref, ваш метод получит указатель на myInt, поэтому он сможет изменить его фактическое значение. Если вы передадите myList с ключевым словом ref, произойдет то же самое. Теперь ваш метод может изменить фактическое значение переменной myList. Он может установить для него другую ссылку.

Вероятно, это легче понять, когда вы видите это на практике, поэтому здесь небольшое консольное приложение, показывающее разницу между двумя методами, которые отличаются только использованием ключевого слова ref * 1033. *

static void Main(string[] args)
{
    //create new instane of List<int> on the heap and store reference to it on the stack in 'myList 'variable
    List<int> myList = new List<int>() { 1 };
    //create new instance of int and store it on the stack in 'myInt' variable
    int myint = 2;

    //call MyMethod
    //copy value of myInt (2) to the new stack frame and store it in 'i' variable
    //copy value of myList (reference to List<int>) to the new stack frame and store it in 'list' variable
    MyMethod(myint, myList);
    Console.WriteLine(myint);       //prints 2
    Console.WriteLine(myList[0]);   //prints 4

    //call MyMethod
    //inside new stack frame store pointer to 'myint' variable
    //inside new stack frame store pointer to 'myList' variable
    MyMethod(ref myint, ref myList);
    Console.WriteLine(myint);       //prints 3
    Console.WriteLine(myList[0]);   //prints 5

    Console.ReadLine();
}

static void MyMethod(int i, List<int> list)
{
    //store value of 3 on stack in variable 'i'
    i = 3;
    //use reference stored on stack in 'list' variable to find our instance of List<int> on the heap and store 4 under index 0
    list[0] = 4;
    //create new instane of List<int> on the heap and store reference to it on the stack, in 'list 'variable
    list = new List<int>() { 5 };
}

static void MyMethod(ref int i, ref List<int> list)
{
    //use pointer to 'myInt' variable to store value of 3 in 'myInt' variable
    i = 3;
    //use pointer to 'myList' variable to get reference stored in it and use that reference to find our instance of List<int> on the heap and store 4 under index 0
    list[0] = 4;
    //create new instane of List<int> on the heap and use pointer to 'myList' variable to store reference to that new instance inside 'myList' variable
    list = new List<int>() { 5 };
}
...