Шаг 69.
Язык программирования C#. Начала
Статические методы. Механизмы передачи аргументов методу

    На этом шаге мы рассмотрим особенности передачи параметров методу по значению.

    Есть два механизма (или способа) передачи аргументов в метод:

    По умолчанию в 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]++;
в теле цикла). После этого снова отображается содержимое массива.


При отображении содержимого массивов в программе использован вспомогательный метод ArrayToText(). Аргументом методу передается ссылка на целочисленный одномерный массив. Результатом метод возвращает текстовую строку, которая содержит значения элементов массива. Значения заключены в квадратные скобки и разделены запятыми. Поэтому для отображения содержимого массива достаточно передать массив аргументом методу ArrayToText() и вывести на экран результат, возвращаемый методом.

    Методу charlie() так же, как и методу bravo(), передается ссылка на целочисленный массив. Но операции, выполняемые с аргументом и массивом, в данном случае несколько сложнее. В частности, при выполнении метода charlie() создается новый массив m: команда

  int[] m = new int[n.Length]);   . 
Размер этого массива точно такой же, как и размер массива n, переданного аргументом методу. Новый массив заполняется поэлементно. Для этого использована конструкция цикла, в теле которой командой
  m[k] = n[k] + 1;
значение элемента созданного массива вычисляется как соответствующее значение переданного аргументом массива, увеличенное на единицу. После завершения конструкции цикла командой
  n = m;
выполняется попытка изменить значение аргумента. Если подойти к этой команде чисто формально, то мы пытаемся перебросить ссылку из аргумента n с исходного массива (переданного через ссылку аргументом методу) на массив, который был создан в методе (хотя в реальности все не так просто). После выполнения команды проверяется содержимое массива, на который ссылается переменная n.

    В главном методе программы объявляется переменная А со значением 100 и объявляются и инициализируются два целочисленных массива В и С. Мы проверяем работу методов alpha(), bravo() и charlie():

    Результат выполнения программы такой.


Рис.1. Результат работы приложения

    Что мы видим? Значение 100 переменной А после вызова метода alpha() не изменилось, хотя при второй проверке аргумента в методе alpha() значение было 101. Чтобы понять этот "парадокс", следует учесть, что аргументы по умолчанию передаются по значению. Поэтому при выполнении команды alpha(А) вместо переменной А в метод alpha() передается техническая копия этой переменной. Ее значение - такое же, как у переменной А. Поэтому при проверке значения аргумента появляется значение 100. При попытке увеличить значение аргумента метода на 1 в действительности увеличивается значение технической переменной. Ее же значение проверяется в методе, и получаем число 101. Когда метод alpha() завершает работу, техническая переменная удаляется из памяти. А переменная А остается со своим исходным значением 100.

    Что касается массива В, то он после выполнения команды bravo(В) изменился. Причина в том, что здесь на самом деле мы не пытаемся изменить аргумент метода. Им является ссылка на массив, а изменяем мы массив. Более детально все происходит следующим образом. При передаче переменной В аргументом методу bravo() для нее создается техническая копия. Значение у копии переменной В такое же, как и у самой переменной В. И это адрес массива. Поэтому и переменная В, и ее копия ссылаются на один и тот же массив. Когда при выполнении метода bravo() изменяются значения элементов массива, то, хотя они изменяются через копию переменной В, речь идет о том же массиве, на который ссылается переменная В. Отсюда и результат.

    Иная ситуация при вызове метода chalrie() с аргументом С. Массив, на который ссылается переменная С, после вызова метода не изменяется. Причина в том, что в теле метода charlie() мы пытаемся записать в переменную, переданную аргументом, новую ссылку. Но поскольку, как и во всех предыдущих случаях, аргументом передается не сама переменная, а ее копия, то новое значение присваивается копии переменной С. Копия ссылается на новый созданный массив. Когда мы проверяем содержимое массива (после присваивания n=m) в теле метода, то проверяется новый созданный массив. Массив, на который ссылается переменная С, не меняется.

    На следующем шаге мы продолжим изучение этого вопроса.




Предыдущий шаг Содержание Следующий шаг