C # Почему массив аргументов должен передаваться с модификатором ref, если массивы передаются по ссылке? - PullRequest
2 голосов
/ 01 декабря 2019

Я не понимаю, почему эта функция была написана так:

System.Array.Resize<int>(ref int[], int)

Если массивы по умолчанию передаются по ссылке, почему не написано так:

System.Array.Resize<int>(int[], int)

Ответы [ 3 ]

4 голосов
/ 01 декабря 2019

Это связано с тем, что когда мы записываем переменную в объект ссылочного типа, существует вид двух частей: фактический экземпляр объекта и ссылка, которую представляет имя переменной (указатель адреса памяти 32-битный или 64-битный внутри, зависит от платформы). Вы можете ясно видеть это с помощью этого фрагмента sharplab.io .

Когда мы вызываем метод, этот указатель копируется, а экземпляр - нет, поэтому:

var a = new Blah {Prop = "1"}; // Blah is a class, a reference type
Method(a);

void Method(Blah blah)
{
    blah.Prop = "2"; // changes the shared instance, we know this.

    blah = new Blah {Prop = "3"}; // has no effect.
}

Console.WriteLine(a.Prop); // 2

Видите ли, когда мы устанавливаем blah внутри метода, мы изменяем нашу локальную ссылку, а не общую. Теперь, если мы используем ключевое слово ref:

var a = new Blah {Prop = "1"};
Method(ref a);

void Method(ref Blah blah)
{
    blah.Prop = "2"; // changes the shared instance, we know this.

    blah = new Blah {Prop = "3"}; // now changes ref a outside
}

Console.WriteLine(a.Prop); // 3!

, поскольку параметр blah передается по ссылке, когда мы его мутируем, мы мутируем исходную ссылку a.

2 голосов
/ 01 декабря 2019

Массивы действительно являются ссылочными типами, что означает, что изменения, внесенные в переданный объект массива внутри метода, будут отражены на стороне вызывающего:

public static void Foo(int[] a) {
    a[0] = 1;
}

// ...
int[] a = new int[1];
Foo(a);
Console.WriteLine(a[0]); // 1

Однако, если вы установите массив в нечтоостальное внутри метода:

public static void Foo(int[] a) {
    a = null;
}

// ...
int[] a = new int[1];
Foo(a);
Console.WriteLine(a[0]); // will not throw NRE

Объявление параметра как ref позволит переназначить параметры для отражения на стороне вызывающего.

Изменение размера массива требует создание нового массива и, следовательно, переназначение параметра. Вы не можете изменить размер массива, изменяя каким-либо образом существующий объект массива. Вот почему он должен быть объявлен как ref.

1 голос
/ 01 декабря 2019

Проще говоря, если ваш массив передается методу в качестве параметра ref, он может быть заменен в целом другим массивом, созданным в методе. Это не относится к массивам, переданным без ключевого слова ref. Код ниже иллюстрирует разницу. Обратите внимание, что отдельные элементы параметров массива могут быть заменены в обоих случаях (без ключевого слова ref).

class Program
{
    static void PrintArr(string comment, int[] arr)
    {
        Console.WriteLine($"{comment}:");
        Console.WriteLine(string.Join(", ", arr.Select(e => e.ToString())));
    }
    static void NoRef(int[] arr)
    {
        int[] localArr = { 2, 4, 8, 10 };
        arr = localArr;
    }
    static void ByRef(ref int[] arr)
    {
        int[] localArr = { 2, 4, 8, 10 };
        arr = localArr;
    }

    static void Main()
    {
        int[] arr;
        arr = new int[] { 1, 3, 4, 7, 9 };

        PrintArr("Before NoRef is called", arr);
        NoRef(arr);
        PrintArr("After NoRef is called", arr);

        PrintArr("Before ByRef is called", arr);
        ByRef(ref arr);
        PrintArr("After ByRef is called", arr);

        Console.ReadLine();
    }
}

}

Вывод для кода показан ниже (обратите внимание, что ByRefКод метода заменил массив.

До вызова NoRef:

1, 3, 4, 7, 9

После вызова NoRef:

1, 3, 4, 7, 9

До вызова ByRef:

1, 3, 4, 7, 9

После вызова ByRef:

2, 4, 8, 10

...