Параметры методов (Справочник по 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 указывает, что этот параметр передается по ссылке и записывается вызванным методом.
См. также
- Справочник по C#
- Ключевые слова в C#
- Списки аргументов в спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.