Параметры методов (Справочник по C#)

В C# аргументы могут передаваться параметрам либо по значению, либо по ссылке. Помните, что типы C# могут быть ссылочными типами (class) или типами значений (struct):

  • Передача по значению означает передачу копии переменной в метод .
  • Передача по ссылке означает передачу доступа к переменной методу .
  • Переменная ссылочного типа содержит ссылку на ее данные.
  • Переменная типа значения содержит данные напрямую.

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

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

Различия демонстрируются в выходных данных следующего примера. Значение поля willIChange экземпляра класса изменяется в результате вызова метода ClassTaker, поскольку этот метод находит указанное поле экземпляра класса по адресу, содержащемуся в параметре. Поле willIChange структуры в вызывающем методе не изменяется в результате вызова метода StructTaker, поскольку значением аргумента является копия самой структуры, а не ее адреса. StructTaker изменяет саму копию, которая будет утрачена после завершения вызова StructTaker.

class TheClass
{
    public string? willIChange;
}

struct TheStruct
{
    public string willIChange;
}

class TestClassAndStruct
{
    static void ClassTaker(TheClass c)
    {
        c.willIChange = "Changed";
    }

    static void StructTaker(TheStruct s)
    {
        s.willIChange = "Changed";
    }

    public static void Main()
    {
        TheClass testClass = new TheClass();
        TheStruct testStruct = new TheStruct();

        testClass.willIChange = "Not Changed";
        testStruct.willIChange = "Not Changed";

        ClassTaker(testClass);
        StructTaker(testStruct);

        Console.WriteLine("Class field = {0}", testClass.willIChange);
        Console.WriteLine("Struct field = {0}", testStruct.willIChange);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Class field = Changed
    Struct field = Not Changed
*/

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

Передача типа значения по значению

При передаче типа значенияпо значению:

  • Если метод назначает параметр для ссылки на другой объект, эти изменения не видны вызывающему объекту.
  • Если метод изменяет состояние объекта, на который ссылается параметр , эти изменения не видны вызывающей объекту.

Следующий пример демонстрирует передачу параметров типа значения по значению. Переменная n передается по значению в метод SquareIt. Любые изменения, выполненные внутри метода, не влияют на исходное значение переменной.

int n = 5;
System.Console.WriteLine("The value before calling the method: {0}", n);

SquareIt(n);  // Passing the variable by value.
System.Console.WriteLine("The value after calling the method: {0}", n);

// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();

static void SquareIt(int x)
// The parameter x is passed by value.
// Changes to x will not affect the original value of x.
{
    x *= x;
    System.Console.WriteLine("The value inside the method: {0}", x);
}
/* Output:
    The value before calling the method: 5
    The value inside the method: 25
    The value after calling the method: 5
*/

Переменная n имеет тип значения. Она содержит данные, в нашем примере это значение 5. Когда вызывается SquareIt, содержимое n копируется в параметр x, который используется исключительно внутри метода. Но в Main значение n всегда останется прежним после выполнения метода SquareIt. Изменения, выполненные внутри метода, влияют только на локальную переменную x.

Передача типа значения по ссылке

При передаче типа значенияпо ссылке:

  • Если метод назначает параметр для ссылки на другой объект, эти изменения не видны вызывающему объекту.
  • Если метод изменяет состояние объекта, на который ссылается параметр , эти изменения видны вызывающей объекту.

Следующий пример полностью совпадает с предыдущим, но теперь аргумент передается как параметр ref. Значение базового аргумента n будет изменяться, когда метод изменяет значение x.

int n = 5;
System.Console.WriteLine("The value before calling the method: {0}", n);

SquareIt(ref n);  // Passing the variable by reference.
System.Console.WriteLine("The value after calling the method: {0}", n);

// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();

static void SquareIt(ref int x)
// The parameter x is passed by reference.
// Changes to x will affect the original value of x.
{
    x *= x;
    System.Console.WriteLine("The value inside the method: {0}", x);
}
/* Output:
    The value before calling the method: 5
    The value inside the method: 25
    The value after calling the method: 25
*/

В этом примере передается не значение n, а ссылка на n. Теперь параметр x будет не значением типа int, а ссылкой на int. В нашем примере это ссылка на n. Таким образом, при передаче x в метод передается только информация о том, что x ссылается на n.

Передача ссылочного типа по значению

При передаче ссылочного типа по значению:

  • Если метод назначает параметр для ссылки на другой объект, эти изменения не видны вызывающему объекту.
  • Если метод изменяет состояние объекта, на который ссылается параметр , эти изменения видны вызывающей объекту.

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

int[] arr = { 1, 4, 5 };
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);

Change(arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);

static void Change(int[] pArray)
{
    pArray[0] = 888;  // This change affects the original element.
    pArray = new int[5] { -3, -1, -2, -3, -4 };   // This change is local.
    System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: 888
*/

В предыдущем примере массив arr имеет ссылочный тип и передается в метод без параметра ref. В таком случае в метод будет передана копия ссылки, которая указывает на arr. Как видно из выходных данных, метод может изменять содержимое элемента массива (в данном случае с 1 на 888). Тем не менее при выделении нового блока памяти с помощью оператора new внутри метода Change переменная pArray будет ссылаться на новый массив. Таким образом, любые выполненные после этого изменения не будут отражаться в исходном массиве arr, который был создан внутри Main. Фактически, в этом примере создается два массива: один внутри Main, а другой — в методе Change.

Передача ссылочного типа по ссылке

При передаче ссылочного типа по ссылке:

  • Если метод назначает параметр для ссылки на другой объект, эти изменения будут видны вызывающему объекту.
  • Если метод изменяет состояние объекта, на который ссылается параметр , эти изменения видны вызывающей объекту.

Следующий пример аналогичен предыдущему, однако в нем в заголовок и вызов метода добавляется ключевое слово ref. Любые изменения, выполняемые в методе, влияют на исходную переменную в вызывающей программе.

int[] arr = { 1, 4, 5 };
System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);

Change(ref arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);

static void Change(ref int[] pArray)
{
    // Both of the following changes will affect the original variables:
    pArray[0] = 888;
    pArray = new int[5] { -3, -1, -2, -3, -4 };
    System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: -3
*/

Все изменения, выполняемые внутри метода, влияют на исходный массив в Main. Фактически, происходит перемещение исходного массива с помощью оператора new. Таким образом, после вызова метода Change любые ссылки на arr будут указывать на массив из пяти элементов, который создается в методе Change.

Область ссылок и значений

Методы могут хранить значения параметров в полях. Когда параметры передаются по значению, это всегда безопасно. Значения копируются, а ссылочные типы доступны при хранении в поле. Для безопасной передачи параметров по ссылке компилятор должен определить, когда безопасно назначить ссылку новой переменной. Для каждого выражения компилятор определяет область , которая ограничивает доступ к выражению или переменной. Компилятор использует две области: safe_to_escape и ref_safe_to_escape.

  • Область safe_to_escape определяет область, в которой можно безопасно получить доступ к любому выражению.
  • Область ref_safe_to_escape определяет область, в которой можно безопасно получить или изменить ссылку на любое выражение.

В неформальном режиме эти области можно рассматривать как механизм, гарантирующий, что код никогда не обращается к недействительной ссылке или не изменяет ее. Ссылка действительна, если она ссылается на допустимый объект или структуру. Область safe_to_escape определяет, когда переменная может быть назначена или переназначаема. Область ref_safe_to_escape определяет, когда переменная может быть назначена ссылочные или ссылочные . Присваивает переменную новому значению; Назначение ссылки присваивает переменной ссылку на другое место хранения.

Модификаторы

Параметры, объявленные для метода без in, ref или out, передаются в вызываемый метод по значению. Модификаторы ref, inи out отличаются правилами назначения:

  • Аргумент для ref параметра должен быть определенно назначен. Вызываемый метод может переназначить этот параметр.
  • Аргумент для in параметра должен быть определенно назначен. Вызываемый метод не может переназначить этот параметр.
  • Аргумент для out параметра не требуется однозначно назначать. Вызываемый метод должен назначить параметр .

В этом разделе описываются ключевые слова, которые можно использовать при объявлении параметров метода:

  • params указывает, что этот параметр может принимать переменное количество аргументов.
  • in указывает, что этот параметр передается по ссылке, но лишь считывается вызванным методом.
  • ref указывает, что этот параметр передается по ссылке и может быть считан или записан вызванным методом.
  • out указывает, что этот параметр передается по ссылке и записывается вызванным методом.

См. также