На этом шаге мы рассмотрим особенности передачи параметров методу по значению.
Есть два механизма (или способа) передачи аргументов в метод:
По умолчанию в C# аргументы в метод передаются по значению. Это подразумевает, что в действительности в метод передается не та переменная, которую мы непосредственно указали аргументом метода при вызове, а ее техническая копия. Каждый раз, когда мы вызываем некоторый метод и передаем ему аргументы, автоматически для каждого из аргументов создается копия. Все операции при выполнении команд метода выполняются с этой копией. В большинстве случаев не играет особой роли, как именно аргументы передаются в метод. Важным это становится, когда мы хотим при вызове метода изменить значения аргументов, переданных ему. Для большей конкретики рассмотрим пример, представленный ниже.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace pr69_1 { class Program { // Первый метод. Аргумент - целое число: static void alpha(int n) { // Проверка значения аргумента: Console.WriteLine("В методе alpha(). На входе: " + n); // Попытка изменить значение аргумента: n++; // Проверка значения аргумента: Console.WriteLine("В методе alpha(). На выходе: " + n); } // Второй метод. Аргумент - ссыпка на массив: static void bravo(int[] n) { // Проверка содержимого массива: Console.WriteLine("В методе bravo(). На входе: " + ArrayToText(n)); // Перебор элементов массива: for(int k=0; k < n.Length; k++){ // Попытка изменить значение элемента массива: n[k]++; } // Проверка содержимого массива: Console.WriteLine("В методе bravo(). На выходе: " +ArrayToText(n)); } // Третий метод. Аргумент - ссылка на массив: static void charlie(int[] n){ // Проверка содержимого массива: Console.WriteLine("В методе charlie(). На входе: " + ArrayToText(n)); // Создается новый массив: int[] m = new int [n.Length]; // Перебор элементов в массиве: for(int k=0; k < n.Length; k++){ // Значение элемента массива: m[k] = n[k] + 1; } // Попытка присвоить новое значение аргументу: n = m; // Проверка содержимого массива: Console.WriteLine("В методе charlie(). На выходе: " + ArrayToText(n)); } // Метод для преобразования массива в текст: static string ArrayToText(int[] n){ // Текстовая переменная: string res = "[" + n[0]; // Перебор элементов массива (кроме начального): for(int k=1; k < n.Length; k++){ // Дописывание текста в текстовую переменную: res += "," + n[k]; } // Дописывание текста з текстовую переменную: res += "]"; // Результат метода - текстовая строка: return res; } // Главный метод программы: static void Main() { // Переменная для передачи аргументом методу: int A = 100; // Проверка значения переменной: Console.WriteLine("До вызова метода alpha(): А=" + A); // Вызов метода: alpha(A); // Проверка значения переменной: Console.WriteLine("После вызова метода alpha(): А=" + A); // Массив для передачи аргументом методу: int[] B = {1, 3, 5}; // Проверка содержимого массива: Console.WriteLine("До вызова метода bravo(): B=" + ArrayToText(B)); // Вызов метода: bravo(B); // Проверка содержимого массиза: Console.WriteLine("После вызова метода bravo(): B=" + ArrayToText(B)); // Массив для передачи аргументом методу: int[] C = {2, 4, 6}; // Проверка содержимого массива: Console.WriteLine("До вызова метода charlie(): C=" + ArrayToText(C)); // Вызов метода: charlie(C); // Проверка содержимого массива: Console.WriteLine("После вызова метода charlie(): C=" + ArrayToText(C)); // Задержка: Console.ReadLine(); } } }
В программе описаны три метода: alpha(), bravo() и charlie(). В каждом из этих методов выполняется попытка изменить аргумент (или создается иллюзия, что аргумент изменяется). Но аргументы у методов разные. У метода alpha() аргумент - целое число. У методов bravo() и charlie() аргумент - ссылка на одномерный целочисленный массив. Проанализируем коды этих методов. Начнем с метода alpha().
При вызове метода появляется сообщение: в консольное окно выводится значение аргумента (обозначен как n), переданного методу. После этого командой
n++;
При выполнении метода bravo() в консольном окне отображается содержимое массива (обозначен как n), переданного аргументом методу. Затем с помощью конструкции цикла перебираются все элементы массива, и значение каждого элемента увеличивается на единицу (команда
n[k]++;
Методу charlie() так же, как и методу bravo(), передается ссылка на целочисленный массив. Но операции, выполняемые с аргументом и массивом, в данном случае несколько сложнее. В частности, при выполнении метода charlie() создается новый массив m: команда
int[] m = new int[n.Length]); .
m[k] = n[k] + 1;
n = m;
В главном методе программы объявляется переменная А со значением 100 и объявляются и инициализируются два целочисленных массива В и С. Мы проверяем работу методов alpha(), bravo() и charlie():
Результат выполнения программы такой.
Рис.1. Результат работы приложения
Что мы видим? Значение 100 переменной А после вызова метода alpha() не изменилось, хотя при второй проверке аргумента в методе alpha() значение было 101. Чтобы понять этот "парадокс", следует учесть, что аргументы по умолчанию передаются по значению. Поэтому при выполнении команды alpha(А) вместо переменной А в метод alpha() передается техническая копия этой переменной. Ее значение - такое же, как у переменной А. Поэтому при проверке значения аргумента появляется значение 100. При попытке увеличить значение аргумента метода на 1 в действительности увеличивается значение технической переменной. Ее же значение проверяется в методе, и получаем число 101. Когда метод alpha() завершает работу, техническая переменная удаляется из памяти. А переменная А остается со своим исходным значением 100.
Что касается массива В, то он после выполнения команды bravo(В) изменился. Причина в том, что здесь на самом деле мы не пытаемся изменить аргумент метода. Им является ссылка на массив, а изменяем мы массив. Более детально все происходит следующим образом. При передаче переменной В аргументом методу bravo() для нее создается техническая копия. Значение у копии переменной В такое же, как и у самой переменной В. И это адрес массива. Поэтому и переменная В, и ее копия ссылаются на один и тот же массив. Когда при выполнении метода bravo() изменяются значения элементов массива, то, хотя они изменяются через копию переменной В, речь идет о том же массиве, на который ссылается переменная В. Отсюда и результат.
Иная ситуация при вызове метода chalrie() с аргументом С. Массив, на который ссылается переменная С, после вызова метода не изменяется. Причина в том, что в теле метода charlie() мы пытаемся записать в переменную, переданную аргументом, новую ссылку. Но поскольку, как и во всех предыдущих случаях, аргументом передается не сама переменная, а ее копия, то новое значение присваивается копии переменной С. Копия ссылается на новый созданный массив. Когда мы проверяем содержимое массива (после присваивания n=m) в теле метода, то проверяется новый созданный массив. Массив, на который ссылается переменная С, не меняется.
На следующем шаге мы продолжим изучение этого вопроса.