C#. Передача параметров в методы

Параметры методов

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

Большая часть программистов сталкивается с необходимостью изучения данной темы, когда приходится изменять параметры, передаваемые в методы (не использовать их значения для вычисления чего-либо, а именно изменять значения, так, чтобы эти изменения сохранились после выполнения метода). И существует ряд нюансов, которые совсем не очевидны для начинающего программиста.

По умолчанию, в языке программирования C#, аргументы в методы передаются по значению! Но, Вы должны помнить, что большинство типов, используемых программистами, являются всё-таки ссылочными типами, и в результате, в метод попадает, так сказать, копия ссылки, так что, Вы можете изменять объект, на который ссылается эта копия (т.е. тот же объект, на который ссылает и оригинал ссылки), но не можете заставить оригинальную ссылку указывать на другой объект. А вот при использовании типов значений, ситуация иная, и проблемы могут всплыть сразу же!

Давайте представим, что нам нужно написать метод, который принимает два целых числа и меняет местами их значения, выглядеть такой метод может примерно так:

//Принимает два целых числа, и меняет их местами
static void Swap(int aFirstArg, int aSecondArg)
{
    //Сохраняем значение первого аргумента во временной переменной
    int tmpParam = aFirstArg;

    //Присваиваем значение второго аргумента первому
    aFirstArg = aSecondArg;
    //Присваиваем сохраненное значение первого аргумента второму
    aSecondArg = tmpParam;
}

Казалось бы, ничего проще нет, но на практике, такой метод работать не будет! Если мы напишем код подобный следующему:

//Использования метода Swap
int a = 5;
int b = 10;

Console.WriteLine("До вызова Swap: \ta = " + a + "; b = " + b);

//Поменять значения a и b местами
Swap(a, b);

Console.WriteLine("После вызова Swap: \ta = " + a + "; b = " + b);

То мы увидим примерно такой результат работы приложения:

Результат работы метода Swap

Результат работы метода Swap

Такой результат работы является следствием того, что аргументы передаются по значению, т.е. в метод передается копия аргумента, и сколько бы мы её не изменяли, за пределами метода, оригинал останется неизменным. Но ситуацию, можно исправить очень просто! Нужно указать при объявлении метода, что параметры должны передаваться по ссылке. Для этого в C# существует ключевое слово ref, которое указывается перед аргументом при объявлении и при вызове метода. На практике, доработанный метод «Swap» это выглядит так:

//Принимает по ссылке два целых числа, и меняет их местами 
static void Swap(ref int aFirstArg, ref int aSecondArg)
{
    //Сохраняем значение первого аргумента во временной переменной
    int tmpParam = aFirstArg;

    //Присваиваем значение второго аргумента первому
    aFirstArg = aSecondArg;
    //Присваиваем сохраненное значение первого аргумента второму
    aSecondArg = tmpParam;
}

А вызов метода «Swap» будет таким:

//Поменять значения a и b местами
Swap(ref a, ref b);

И тогда, результат работы метода, будет таким:

Результат работы доработанного метода Swap

Результат работы доработанного метода Swap

Как видите, подобные проблемы решаются передачи параметров в методы решаются довольно просто, даже не пришлось переписывать «логику» метода. Но есть одна особенность, объект, который передается в качестве ref-параметра метода, должен быть инициализирован перед вызовом метода. Т.е. подобный код, компилироваться не будет:

//Использования метода Swap
int a; //Переменная не инициализирована!!!
int b = 10;

//Поменять значения a и b местами
Swap(a, b);

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

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

//Принимает выходной параметр, из устанавливает ему случайное значение
static void SetValue(out int aParam)
{
    //Создаем объект, генерирующий случайные числа
    Random rnd = new Random();
    //Генерируем число
    aParam = rnd.Next();
}

Использовать метод можно так:

//Создает не инициализированную переменную
int a;

//Вызываем метод, задающей ей значение
SetValue(out a);

Пример, конечно надуманный, можно было бы не работать с выходными параметрами, а просто возвращать случайное значение методом «SetValue», но в реальной практике, можно столкнуться с ситуацией, что метод уже возвращает какое-то значение, или нужно заполнить несколько выходных параметров.

А теперь вернемся к ссылочным типам! С ними всё немножко иначе, в метод попадает копия ссылки, это значит, что изменять состояние объекта, на который указывает ссылка мы можем, в отличии от типов значений. Но вот поменять местами два объекта, даже ссылочного типа, в «лоб» не получится, всё равно придется прибегать к использованию ссылочных параметров, но это и проблема! Правда?

Добавить комментарий