Найти тему

Методы и модификаторы параметров ((отсутствует), out, ref, params) в C#

Стандартное поведение передачи параметров по значению

По умолчанию параметр передается функции по значению. Другими словами, если аргумент не помечен модификатором параметра, то в функцию передается копия данных. Что именно копируется, зависит от того, относится ли параметр к типу значения или же к ссылочному типу. В настоящий момент предположим, что внутри класса Programm есть представленный далее метод, который оперирует с двумя параметрами числового типа, передаваемыми по значению:

// По умолчанию аргументы передаются по значению.
static int Add(int x, int y)
{
int ans = x + y;
// Вызывающий код не увидит эти изменения,
// т.к. модифицируется копия исходных данных.
x = 10000;
y = 88888;
Console.WriteLine("Вывод переменной X прямо в функции: {0}", x);
Console.WriteLine("Вывод переменной Y прямо в функции: {0}", y);
return ans;
}

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

static void Main(string[] args)
{
Console.WriteLine("Забавы с методами");
// Передать две переменные по значению.
int x = 9, y = 10;
Console.WriteLine("Перед вызовом: X: {0}, Y: {1}", x, y);
Console.WriteLine("Результат метода Add: {0}", Add(x, y));
Console.WriteLine("После вызова: X: {0}, Y: {1}", x, y);
Console.ReadLine();
}

---Вывод в консоль---

Забавы с методами
Перед вызовом: X: 9, Y: 10
Вывод переменной X прямо в функции: 10000
Вывод переменной Y прямо в функции: 88888
Результат метода Add: 19
После вызова: X: 9, Y: 10

Как и следовало ожидать, к тому же данный факт подтверждается показанным выше выводом, значения x и y остаются идентичными до и после вызова метода Add(), потому что элементы данных передавались по значению. Таким образом, любые изменения параметров, производимые внутри метода Add(), вызывающему коду не видны, т.к. метод Add() оперирует на копии данных.

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

Теперь мы рассмотрим выходные параметры. Метод, который был определен для приема выходных параметров (посредством ключевого слова out), перед выходом обязан присваивать им соответствующие значения (иначе компилятор сообщит об ошибке).
В целях демонстрации ниже приведена альтернативная версия метода
Add(), которая возвращает сумму двух целых чисел с применением модификатора out (обратите внимание, что возвращаемым значением метода теперь является void):

// Значения выходных параметров должны быть установлены
// Внутри вызываемого метода.
static void Add(int x, int y, out int ans)
{
ans = x + y;
}

Вызов метода с выходными параметрами также требует использования модификатора out. Однако предварительно устанавливать значения локальных переменных, которые передаются в качестве выходных параметров, вовсе не обязательно (после вызова эти значения все равно будут утеряны). Причина, по которой компилятор позволяет передавать на первый взгляд неинициализированные данные, связана с тем, что вызываемый метод обязан выполнить присваивание. Нет нужды объявлять параметры out до их применения. Другими словами, они могут объявляться внутри вызова метода:

Add(33, 31, out int ans);

В следующем коде представлен пример вызова метода с встраиваемым объявлением параметра out:

static void Main(string[] args)
{
Console.WriteLine("Забавы продолжаются");
...
// Присваивать начальные значения локальным переменным, используемым
// как выходные параметры, не обязательно при условии, что они
// впервые используются впервые в таком качестве,
// Версия C Sharp 7 позволяет объявлять параметры out в вызове метода.
Add(33, 31, out int ans)
int ans;
Add(33,31, out ans);
Console.WriteLine("33 + 31 = {0}", ans);
Console.ReadLine();
}

Предыдущий пример по своей природе предназначен только для иллюстрации, на самом деле нет никаких причин возвращать значение суммы через входной параметр. Тем не менее, модификатор out в C Sharp служит действительно практичной цели, он позволяет вызывающему коду получать несколько выходных значений из единственного вызова метода:

// Возвращение множества выходных параметров.
static void FillTheseValues(out int a, out string b, out bool c)
{
a = 9;
b = "Hello world!.";
c = true;
}

Теперь вызывающий код имеет возможность обращаться к методу FillTheseValues(). Не забывайте, что модификатор out должен применяться как при вызове, так и при реализации метода:

static void Main(string[] args)
{
Console.WriteLine("Забавы");
...
int i; string str; bool b;
FillTheseValues(out i, out str, out b);

Console.WriteLine("Int равен: {0}", i);
Console.WriteLine("String равен: {0}", str);
Console.WriteLine("Bool равен: {0}", b");
Console.ReadLine();
}

---Вывод в консоль---

Забавы
Int равен: 9
String равен: Hello world!.
Bool равен: True

  • На заметку! В версии C Sharp 7 появились кортежи, представляющие собой еще один способ возвращения множества значений из вызова метода.

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

static void ThsWontCompile(out int a)
{
Console.WriteLine("Ошибка! Забыли присвоить значение выходному параметру");
}

Наконец, если значение параметра out не интересует, тогда в качестве заполнителя можно использовать отбрасывание. Например, когда нужно выяснить, имеет ли строка допустимый формат даты, но сама разобранная дата не требуется, модно было бы написать такой код:

if (DataTime.TryParse(dateString, out_))
{
// Ваш код
}

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

А теперь посмотрим, как в C Sharp используется модификатор ref. Ссылочные параметры необходимы, когда вы хотите разрешить методу манипулировать различными элементами данных (и обычно изменять их значения), которые объявлены в вызывающем коде, таком как процедура сортировки или обмена. Обратите внимание на отличия между ссылочными и выходными параметрами.

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

Давайте рассмотрим применение ключевого слова ref на примере метода, меняющего местами значения двух переменных типа string (естественно, здесь мог бы использоваться любой тип данных, включая int, bool, float и т.д.):

// Ссылочные параметры.
public static void SwapStrings(ref string s1, ref string s2)
{
string tempStr = s1;
s1 = s2;
s2 = tempStr;
}

Метод SwapStrings() можно вызывать следующим образом:

static void Main(string[] args)
{
Console.WriteLine("Забава");
...
string str1 = "Flip";
string str2 = "Flop";
Console.WriteLine("До работы метода: {0}, {1}", str1, str2);
SwapStrings(ref str1, ref str2);
Console.WriteLine("После работы метода: {0}, {1}", str1, str2);
Console.ReadLine();
}

---Вывод в консоль---

Забава
До работы метода: Flip, Flop
После работы метода: Flop, Flip

Здесь вызывающий код присваивает начальные значения локальным строковым данным (str1 и str2). После вызова метода SwapStrings() строка str1 будет содержать значение "Flop", а строка str2 - значение "Flip"

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

В языке C Sharp поддерживаются массивы параметров с использованием ключевого слова params, которое позволяет передавать методу переменное количество идентично типизированных параметров (или классов, связанных отношением наследования) в виде единственного логического параметра. Вдобавок аргументы, помеченные ключевым словом params, могут обрабатываться, когда вызывающий код передает строго типизированный массив или список элементов, разделенных запятыми. Да, это может сбивать с толку! В целях прояснения предположим, что вы хотите создать функцию, которая позволяет вызывающему коду передавать любое количество аргументов и возвращает их среднее значение.

Если вы прототипируете данный метод так, чтобы он принимал массив значений double, тогда в вызывающем коде придется сначала определить массив, затем заполнить его значениям и, наконец, передать его методу. Однако если вы определите метод CalculateAverage() как принимающий параметр params типа double[], то вызывающий код может просто передавать список значений double, разделенных запятыми. "За кулисами" исполняющая среда .NET автоматически упакует набор значений double в массив типа double.

// Возвращение среднего из некоторого количества значений double.
static double CalculateAverage (params double[] values)
{
Console.WriteLine("Ты послал мне {0} double.", values.Length);

double sum = 0;
if (values.Length == 0)
return sum;
for (int i = 0; i < values.Length; i++)
sum += values[i];
return (sum / values.Length);
}

Метод CalculateAverage() был определен для приема массива параметров типа double. Фактически он ожидает передачи любого количества (включая ноль) значений double и вычисляет их среднее. Метод может вызываться любым из показанных далее способов:

static void Main(string[] args)
{
Console.WriteLine("Забавы");
...
// Передать список значений double, разделенных запятыми...
double average;
average = CalculateAverage(4.0, 3.2, 5.7, 64.22, 87.2);
Console.WriteLine("Среднее значение данных: {0}", average);

// ... или передать массив значений double.
double[] data = {4.0, 3.2, 5.7};
average = CalculateAverage(data);
Console.WriteLine("Среднее значение данных: {0}", average);

// Среднее из 0 равно 0
Console.WriteLine("Среднее значение данных: {0}", CalculateAverage());
Console.ReadLine();
}

---Вывод в консоль---

Забавы
Ты послал мне 5 double.
Среднее значение данных: 32,864
Ты послал мне 3 double.
Среднее значение данных: 4,3
Ты послал мне 0 double.
Среднее значение данных: 0

Если модификатор params в определении метода CalculateAverage() не задействован, тогда его первый вызов приведет к ошибке на этапе компиляции, т.к. компилятору не удается найти версию CalculateAverage(), принимающую пять аргументов типа double.

  • На заметку! Во избежание любой неоднозначности язык C Sharp требует, чтобы метод поддерживал только один параметр params, который должен быть последним в списке параметров.

Как и можно было догадаться, данный прием- всего лишь удобство для вызывающего кода, потому что среда CLR самостоятельно создает массив по мере необходимости. В момент, когда массив окажется внутри области видимости вызываемого метода, его можно трактовать как полноценный массив .NET, обладающий всей функциональностью базового библиотечного класса System.Array

Конец

Спасибо, если вы прочитали эту статью. Надеюсь вы что-то новое узнали для себя, и конечно же поняли. Подпишитесь, поставьте лайк, напишите комментарий, поддержите меня. Хотелось бы увидеть как улучшать статьи и чего не хватает. Буду анализировать и улучшать контент. Еще раз спасибо, до свидания! :)