Параметры методов
По умолчанию аргументы в C# передаются в функции по значению. Это означает, что копия переменной передается методу. Для типов значений (struct
) копия значения передается методу. Для ссылочных (class
) типов копия ссылки передается методу. Модификаторы параметров позволяют передавать аргументы по ссылке. Следующие понятия помогают понять эти различия и как использовать модификаторы параметров:
- Передача по значению означает передачу копии переменной в метод.
- Передача по ссылке означает передачу доступа к переменной методу.
- Переменная ссылочного типа содержит ссылку на свои данные.
- Переменная типа значения содержит данные напрямую.
Поскольку структура является типом значения, метод получает и работает с копией аргумента структуры при передаче структуры по значению в метод. При этом метод не имеет доступа к исходной структуре в вызывающем методе и, соответственно, никак не может изменить ее. В этом случае метод может изменять только копию.
Экземпляр класса является ссылочным типом, а не типом значения. При передаче ссылочного типа по значению в метод этот метод получает копию ссылки на экземпляр класса. Обе переменные ссылаются на один и тот же объект. Параметр является копией ссылки. Вызывающий метод не может переназначить экземпляр в вызывающем методе. Однако вызываемые методы могут использовать копию ссылки для доступа к членам экземпляра. Если вызываемый метод изменяет член экземпляра, вызывающий метод также видит эти изменения, так как он ссылается на тот же экземпляр.
Различия демонстрируются в выходных данных следующего примера. Метод изменяет значение 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);
}
}
/* Output:
Class field = Changed
Struct field = Not Changed
*/
Сочетания типов параметров и режима аргументов
Как передается аргумент и является ли он ссылочным типом или типом значения, определяет, какие изменения, внесенные в аргумент, видны вызывающему объекту:
- При передаче типа значения по значению:
- Если метод назначает параметр для ссылки на другой объект, эти изменения не отображаются из вызывающего объекта.
- Если метод изменяет состояние объекта, на который ссылается параметр, эти изменения не отображаются вызывающим объектом.
- При передаче ссылочного типа по значению:
- Если метод назначает параметр для ссылки на другой объект, эти изменения не отображаются из вызывающего объекта.
- Если метод изменяет состояние объекта, на который ссылается параметр, эти изменения отображаются из вызывающего объекта.
- При передаче типа значения по ссылке:
- Если метод назначает параметр для ссылки на другой объект с помощью
ref =
, эти изменения не отображаются из вызывающего объекта. - Если метод изменяет состояние объекта, на который ссылается параметр, эти изменения отображаются из вызывающего объекта.
- Если метод назначает параметр для ссылки на другой объект с помощью
- При передаче ссылочного типа по ссылке:
- Если метод назначает параметр для ссылки на другой объект, эти изменения видны вызывающему объекту.
- Если метод изменяет состояние объекта, на который ссылается параметр, эти изменения отображаются из вызывающего объекта.
Передача ссылочного типа по ссылке позволяет вызываемому методу заменять объект, на который указывает ссылочный параметр в вызывающем объекте. Место хранения объекта передается методу в качестве значения ссылочного параметра. Если изменить место хранения параметра (с указанием на новый объект), необходимо изменить место хранения, на который ссылается вызывающий объект. В следующем примере экземпляр ссылочного типа передается как параметр ref
.
class Product
{
public Product(string name, int newID)
{
ItemName = name;
ItemID = newID;
}
public string ItemName { get; set; }
public int ItemID { get; set; }
}
private static void ChangeByReference(ref Product itemRef)
{
// Change the address that is stored in the itemRef parameter.
itemRef = new Product("Stapler", 12345);
}
private static void ModifyProductsByReference()
{
// Declare an instance of Product and display its initial values.
Product item = new Product("Fasteners", 54321);
System.Console.WriteLine("Original values in Main. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);
// Pass the product instance to ChangeByReference.
ChangeByReference(ref item);
System.Console.WriteLine("Calling method. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);
}
// This method displays the following output:
// Original values in Main. Name: Fasteners, ID: 54321
// Calling method. Name: Stapler, ID: 12345
Безопасный контекст ссылок и значений
Методы могут хранить значения параметров в полях. Когда параметры передаются по значению, это обычно безопасно. Значения копируются, а ссылочные типы доступны при хранении в поле. Для безопасного передачи параметров по ссылке требуется, чтобы компилятор определил, когда он безопасно назначить ссылку новой переменной. Для каждого выражения компилятор определяет безопасный контекст , ограничивающий доступ к выражению или переменной. Компилятор использует две области: безопасный контекст и ref-safe-context.
- Безопасный контекст определяет область, к которой можно безопасно получить доступ к любому выражению.
- Контекст ref-safe-определяет область, в которой ссылка на любое выражение может быть безопасно доступ к любому выражению или изменена.
В неофициальном режиме эти области можно рассматривать как механизм, чтобы гарантировать, что код никогда не обращается к ссылке или изменяет ссылку, которая больше не является допустимой. Ссылка действительна, если она ссылается на допустимый объект или структуру. Безопасный контекст определяет, когда переменная может быть назначена или переназначна. Контекст ref-safe-определяет , когда переменная может быть назначена или переназначирована. Назначение назначает переменную новому значению; Назначение ссылок назначает переменную для ссылки на другое расположение хранилища.
Параметры ссылок
К объявлению параметров применяется один из следующих модификаторов для передачи аргументов по ссылке, а не по значению:
ref
: аргумент необходимо инициализировать перед вызовом метода. Метод может назначить новое значение параметру, но не требуется для этого.out
: вызов метода не требуется для инициализации аргумента перед вызовом метода. Метод должен назначить значение параметру.ref readonly
: аргумент необходимо инициализировать перед вызовом метода. Метод не может назначить новому значению параметру.in
: аргумент необходимо инициализировать перед вызовом метода. Метод не может назначить новому значению параметру. Компилятор может создать временную переменную для хранения копии аргумента вin
параметрах.
Члены класса не могут иметь сигнатуры, которые отличаются только по ref
, ref readonly
in
илиout
. Ошибка компилятора возникает, если единственное различие между двумя членами типа заключается в том, что один из них имеет параметр, а другой имеет ref
out
ref readonly
параметр или in
параметр. Однако методы могут быть перегружены, если один метод имеет ref
параметр , in
ref readonly
или out
параметр, а другой имеет параметр, передаваемый значением, как показано в следующем примере. В других ситуациях, требующих сопоставления подписей, таких как скрытие или переопределение, in
, ref
ref readonly
и out
являются частью подписи и не совпадают друг с другом.
Если параметр имеет один из предыдущих модификаторов, соответствующий аргумент может иметь совместимый модификатор:
- Аргумент параметра
ref
должен включатьref
модификатор. - Аргумент параметра
out
должен включатьout
модификатор. - Аргумент для
in
параметра может дополнительно включатьin
модификатор.ref
Если модификатор используется вместо аргумента, компилятор выдает предупреждение. - Аргумент параметра
ref readonly
должен содержать либоin
ref
модификаторы, но не оба. Если модификатор не включен, компилятор выдает предупреждение.
При использовании этих модификаторов они описывают, как используется аргумент:
ref
означает, что метод может считывать или записывать значение аргумента.out
означает, что метод задает значение аргумента.ref readonly
означает, что метод считывает, но не может записывать значение аргумента. Аргумент должен передаваться по ссылке.in
означает, что метод считывает, но не может записывать значение аргумента. Аргумент передается по ссылке или через временную переменную.
Предыдущие модификаторы параметров нельзя использовать в следующих типах методов:
- Асинхронные методы, которые определяются с помощью модификатора async.
- Методы итератора, которые включают оператор yield return или
yield break
.
Методы расширения также имеют ограничения на использование этих ключевых слов аргументов:
- Ключевое
out
слово нельзя использовать в первом аргументе метода расширения. - Ключевое
ref
слово нельзя использовать в первом аргументе метода расширения, если аргумент неstruct
является аргументом или универсальным типом, который не ограничен структурой. ref readonly
Нельзя использовать ключевые слова иin
ключевые слова, если только первый аргумент не является аргументомstruct
.in
Ключевыеref readonly
слова нельзя использовать для любого универсального типа, даже если они ограничены структурой.
Свойства не являются переменными. Это методы. Свойства не могут быть аргументами для ref
параметров.
Модификатор параметра ref
Для использования параметра ref
и при определении метода, и при вызове метода следует явно использовать ключевое слово ref
, как показано в следующем примере. (За исключением того, что вызывающий метод может опускать ref
при вызове COM.)
void Method(ref int refArgument)
{
refArgument = refArgument + 44;
}
int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45
Аргумент, передаваемый параметру, должен быть инициализирован перед передачей ref
.
Модификатор параметра out
Для применения параметра out
определение метода и метод вызова должны явно использовать ключевое слово out
. Например:
int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod); // value is now 44
void OutArgExample(out int number)
{
number = 44;
}
Переменные, передаваемые в качестве out
аргументов, не должны быть инициализированы перед передачей в вызове метода. Но перед передачей управления из вызванного метода он должен присвоить значение.
Деконструктивные методы объявляют свои параметры модификатором out
для возврата нескольких значений. Другие методы могут возвращать кортежи значений для нескольких возвращаемых значений.
Перед передачей переменной в качестве аргумента можно объявить переменную в отдельном операторе out
. Можно также объявить out
переменную в списке аргументов вызова метода, а не в отдельном объявлении переменной. out
Объявления переменных создают более компактный, удобочитаемый код, а также непреднамеренно присваивают переменной значение перед вызовом метода. В следующем примере переменная определяется number
в вызове метода Int32.TryParse .
string numberAsString = "1640";
if (Int32.TryParse(numberAsString, out int number))
Console.WriteLine($"Converted '{numberAsString}' to {number}");
else
Console.WriteLine($"Unable to convert '{numberAsString}'");
// The example displays the following output:
// Converted '1640' to 1640
Вы также можете объявить неявно типизированные локальные переменные.
ref readonly
модификатор
Модификатор ref readonly
должен присутствовать в объявлении метода. Модификатор на сайте вызова является необязательным. in
Можно использовать модификатор или ref
модификатор. Модификатор ref readonly
недействителен на сайте вызова. Какой модификатор, используемый на сайте вызова, может помочь описать характеристики аргумента. Можно использовать ref
только в том случае, если аргумент является переменной и доступен для записи. Можно использовать in
только в том случае, если аргумент является переменной. Это может быть запись или чтение. Нельзя добавить модификатор, если аргумент не является переменной, но является выражением. В следующих примерах показаны эти условия. Следующий метод использует ref readonly
модификатор, чтобы указать, что большая структура должна передаваться по ссылке по причинам производительности:
public static void ForceByRef(ref readonly OptionStruct thing)
{
// elided
}
Метод можно вызвать с помощью ref
модификатора или in
модификатора. Если не указать модификатор, компилятор выдает предупреждение. Если аргумент является выражением, а не переменной, нельзя добавить in
или ref
модификаторы, поэтому вы должны отключить предупреждение:
ForceByRef(in options);
ForceByRef(ref options);
ForceByRef(options); // Warning! variable should be passed with `ref` or `in`
ForceByRef(new OptionStruct()); // Warning, but an expression, so no variable to reference
Если переменная является переменной readonly
, необходимо использовать in
модификатор. Компилятор выдает ошибку, если вместо этого используется ref
модификатор.
Модификатор ref readonly
указывает, что метод ожидает, что аргумент будет переменной, а не выражением, которое не является переменной. Примеры выражений, которые не являются переменными, являются константами, возвращаемыми методом значениями и свойствами. Если аргумент не является переменной, компилятор выдает предупреждение.
Модификатор параметра in
Модификатор in
требуется в объявлении метода, но не требуется на сайте вызова.
int readonlyArgument = 44;
InArgExample(readonlyArgument);
Console.WriteLine(readonlyArgument); // value is still 44
void InArgExample(in int number)
{
// Uncomment the following line to see error CS8331
//number = 19;
}
Модификатор in
позволяет компилятору создать временную переменную для аргумента и передать ссылку на этот аргумент. Компилятор всегда создает временную переменную, когда аргумент должен быть преобразован, при неявном преобразовании из типа аргумента или когда аргумент является значением, которое не является переменной. Например, если аргумент является литеральным значением или значением, возвращаемым методом доступа к свойствам. Если API требует, чтобы аргумент был передан по ссылке, выберите ref readonly
модификатор вместо in
модификатора.
Методы, определенные с помощью in
параметров, потенциально получают оптимизацию производительности. Некоторые struct
аргументы типа могут иметь большой размер, и когда методы вызываются в жестких циклах или критически важных путях кода, стоимость копирования этих структур является существенной. Методы объявляют in
параметры, чтобы указать, что аргументы можно передавать по ссылке безопасно, так как вызываемый метод не изменяет состояние этого аргумента. Передача этих аргументов по ссылке позволяет избежать (потенциально) дорогого копирования. Вы явным образом добавляете модификатор in
в место вызова, чтобы аргумент передавался по ссылке, а не по значению. Явное использование in
приводит к двум результатам.
- Указание
in
на сайте вызова заставляет компилятора выбрать метод, определенный с соответствующимin
параметром. В противном случае, когда два метода отличаются только наличиемin
, перегрузка по значению подходит лучше. - Указывая
in
, вы объявляете намерение передать аргумент по ссылке. Аргумент, используемый сin
, должен представлять расположение, на которое можно сослаться напрямую. Те же общие правила иout
ref
аргументы применяются: нельзя использовать константы, обычные свойства или другие выражения, которые создают значения. В противном случае опущениеin
на сайте вызова сообщает компилятору, что это нормально, чтобы создать временную переменную для передачи ссылки только для чтения в метод. Компилятор создает временную переменную для преодоления нескольких ограничений сin
аргументами:- Временная переменная позволяет использовать константы времени компиляции, например параметры
in
. - Временная переменная позволяет использовать свойства или другие выражения для параметров
in
. - Временная переменная позволяет аргументам, где существует неявное преобразование типа аргумента в тип параметра.
- Временная переменная позволяет использовать константы времени компиляции, например параметры
Во всех предыдущих случаях компилятор создает временную переменную, хранящую значение константы, свойства или другого выражения.
Эти правила проиллюстрированы в следующем коде:
static void Method(in int argument)
{
// implementation removed
}
Method(5); // OK, temporary variable created.
Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // OK, temporary int created with the value 0
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // passed by readonly reference
Method(in i); // passed by readonly reference, explicitly using `in`
Предположим, что еще один метод, использующий аргументы по значению, был доступен. Результаты изменяются, как показано в следующем коде:
static void Method(int argument)
{
// implementation removed
}
static void Method(in int argument)
{
// implementation removed
}
Method(5); // Calls overload passed by value
Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // Calls overload passed by value.
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // Calls overload passed by value
Method(in i); // passed by readonly reference, explicitly using `in`
Аргумент передается по ссылке только в последнем вызове метода.
Примечание.
Для простоты предыдущий код использует int
в качестве типа аргумента. Так как по размеру int
не больше ссылки на большинстве современных компьютеров, не имеет смысла передавать один int
в качестве ссылки, доступной только для чтения.
params
модификатор
Другие параметры не разрешены после ключевого params
слова в объявлении метода, и в объявлении метода разрешено только одно params
ключевое слово.
Объявленный тип параметра должен быть типом params
коллекции. Распознанные типы коллекций:
- Одномерный тип
T[]
массива, в этом случае —T
тип элемента. - Тип диапазона:
System.Span<T>
System.ReadOnlySpan<T>
Ниже приведенT
тип элемента.
- Тип с доступным методом создания с соответствующим типом элемента. Метод создания определяется с помощью того же атрибута, который используется для выражений коллекции.
- Тип структуры или класса , реализующий System.Collections.Generic.IEnumerable<T> :
- Тип имеет конструктор, который можно вызвать без аргументов, и конструктор по крайней мере так же доступен, как декларативный элемент.
- Тип имеет метод
Add
экземпляра (а не расширения), где:- Метод можно вызвать с одним аргументом значения.
- Если метод является универсальным, аргументы типа можно вывести из аргумента.
- Метод по крайней мере доступен как декларативный член. Здесь тип элемента — это тип итерации типа.
- Тип интерфейса:
Перед C# 13 параметр должен быть одниммерным массивом.
При вызове метода с параметром params
можно передать следующие объекты:
- разделенный запятыми список аргументов типа элементов массива;
- Коллекция аргументов указанного типа.
- не передавать аргументы. Если аргументы не отправляются, длина списка
params
равна нулю.
В следующем примере показаны различные способы оправки аргументов параметру params
.
public static void ParamsModifierExample(params int[] list)
{
for (int i = 0; i < list.Length; i++)
{
System.Console.Write(list[i] + " ");
}
System.Console.WriteLine();
}
public static void ParamsModifierObjectExample(params object[] list)
{
for (int i = 0; i < list.Length; i++)
{
System.Console.Write(list[i] + " ");
}
System.Console.WriteLine();
}
public static void TryParamsCalls()
{
// You can send a comma-separated list of arguments of the
// specified type.
ParamsModifierExample(1, 2, 3, 4);
ParamsModifierObjectExample(1, 'a', "test");
// A params parameter accepts zero or more arguments.
// The following calling statement displays only a blank line.
ParamsModifierObjectExample();
// An array argument can be passed, as long as the array
// type matches the parameter type of the method being called.
int[] myIntArray = { 5, 6, 7, 8, 9 };
ParamsModifierExample(myIntArray);
object[] myObjArray = { 2, 'b', "test", "again" };
ParamsModifierObjectExample(myObjArray);
// The following call causes a compiler error because the object
// array cannot be converted into an integer array.
//ParamsModifierExample(myObjArray);
// The following call does not cause an error, but the entire
// integer array becomes the first element of the params array.
ParamsModifierObjectExample(myIntArray);
}
/*
Output:
1 2 3 4
1 a test
5 6 7 8 9
2 b test again
System.Int32[]
*/
Разрешение перегрузки может вызвать неоднозначность, если аргумент параметра params
является типом коллекции. Тип коллекции аргумента должен быть преобразован в тип коллекции параметра. Если разные перегрузки обеспечивают лучшие преобразования для этого параметра, этот метод может быть лучше. Однако если аргумент параметра params
является дискретными элементами или отсутствует, все перегрузки с разными params
типами параметров равны для этого параметра.
Дополнительные сведения см. в разделе списков аргументов в спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.