Глиф Учебники

Урок 8. Методы — свои функции

Курс «C#» · урок 8 из 10 · 4 минуты чтения

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

Первый метод

В этом уроке программы написаны в классической форме — с классом Program и методом Main (урок Установка .NET и первая программа). Свои методы пишутся рядом с Main, внутри того же класса:

C#
class Program
{
    static void Main()
    {
        PrintLine(10);
        int s = Sum(2, 3);
        Console.WriteLine(s);
        Console.WriteLine(Max(4, 9));
        PrintLine(10);
    }

    static void PrintLine(int length)
    {
        Console.WriteLine(new string('-', length));
    }

    static int Sum(int a, int b)
    {
        return a + b;
    }

    static int Max(int a, int b) => a > b ? a : b;
}
Вывод
----------
5
9
----------

Из чего состоит заголовок static int Sum(int a, int b):

static int Sum(int a, int b) без объекта имя параметры тип результата вызов int s = Sum(2, 3); s 5 метод Sum a 2 b 3 return a + b; 2 + 3 = 5 аргументы 2 и 3 результат 5
Заголовок метода Sum состоит из static, типа результата, имени и параметров; при вызове Sum(2, 3) аргументы попадают в a и b, а return возвращает 5 в переменную s.

Если метод — одно выражение, его можно записать стрелкой: => a > b ? a : b;, как Max. new string('-', length) создаёт строку из length одинаковых символов.

return

В методе с типом результата return должен срабатывать на любом пути выполнения. Иначе — ошибка not all code paths return a value (CS0161):

C#
static string Sign(int n)
{
    if (n > 0)
    {
        return "плюс";
    }
    if (n < 0)
    {
        return "минус";
    }
    return "ноль";
}

Без последней строки компилятор не знает, что вернуть при n == 0, и программа не соберётся. В методе void можно написать return; без значения — чтобы выйти раньше.

Параметры по умолчанию и именованные аргументы

У параметра можно задать значение по умолчанию — тогда его разрешено не передавать. А аргументы можно называть по именам и писать в любом порядке:

C#
class Program
{
    static void Main()
    {
        Greet("Анна");
        Greet("Борис", "Здравствуйте");
        Greet(greeting: "Добрый день", name: "Вера");
    }

    static void Greet(string name, string greeting = "Привет")
    {
        Console.WriteLine($"{greeting}, {name}!");
    }
}
Вывод
Привет, Анна!
Здравствуйте, Борис!
Добрый день, Вера!

Перегрузка

В одном классе может быть несколько методов с одним именем, если у них разные параметры. Это перегрузка: компилятор выбирает нужный вариант по аргументам.

C#
class Program
{
    static void Main()
    {
        Console.WriteLine(Area(5));
        Console.WriteLine(Area(3, 4));
    }

    static int Area(int side) => side * side;

    static int Area(int width, int height) => width * height;
}
Вывод
25
12

Console.WriteLine — тоже перегруженный метод: у него есть варианты для строк, чисел, символов и других типов.

Как передаются аргументы: ref и out

Обычно метод получает копию значения, и изменения внутри метода снаружи не видны. Ключевое слово ref передаёт саму переменную, out — переменную, в которую метод обязан положить результат:

C#
class Program
{
    static void Main()
    {
        int n = 5;
        AddTen(n);
        Console.WriteLine(n);
        AddTenRef(ref n);
        Console.WriteLine(n);

        Divide(17, 5, out int quotient, out int remainder);
        Console.WriteLine($"{quotient} и {remainder} в остатке");
    }

    static void AddTen(int x)
    {
        x += 10;
    }

    static void AddTenRef(ref int x)
    {
        x += 10;
    }

    static void Divide(int a, int b, out int q, out int r)
    {
        q = a / b;
        r = a % b;
    }
}
Вывод
5
15
3 и 2 в остатке
AddTen(n); Main n 5 копия AddTen(int x) x += 10 x 15 после вызова n — по-прежнему 5 AddTenRef(ref n); Main n 15 AddTenRef(ref int x) x += 10 x это n после вызова n стало 15
Обычный параметр x получает копию числа, и n в Main остаётся 5, а параметр с ref — это сама переменная n, поэтому она становится 15.

ref и out пишут и в объявлении, и в вызове. С out вы уже встречались: int.TryParse(text, out int value) возвращает bool, а само число кладёт в out-переменную.

Массивы, списки и объекты классов передаются по ссылке на них: если метод поменяет элемент массива, это будет видно и снаружи.

params и рекурсия

params позволяет передать сколько угодно аргументов — внутри метода они видны как массив. А рекурсия — это когда метод вызывает сам себя:

C#
class Program
{
    static void Main()
    {
        Console.WriteLine(SumAll(1, 2, 3));
        Console.WriteLine(SumAll(10, 20, 30, 40));
        Console.WriteLine(Factorial(5));
        Console.WriteLine(Factorial(20));
    }

    static int SumAll(params int[] numbers)
    {
        int sum = 0;
        foreach (int x in numbers)
        {
            sum += x;
        }
        return sum;
    }

    static long Factorial(int n)
    {
        if (n <= 1)
        {
            return 1;
        }
        return n * Factorial(n - 1);
    }
}
Вывод
6
100
120
2432902008176640000

У рекурсии обязательно должно быть условие остановки (n <= 1), иначе вызовы не кончатся, и программа упадёт с переполнением стека — памяти под вызовы методов (Stack overflow). Факториал быстро растёт, поэтому результат — long: 20! уже не помещается в int.

вызовы идут вниз ответы идут вверх Factorial(5) 5 * Factorial(4) 5 * 24 = 120 Factorial(4) 4 * Factorial(3) 4 * 6 = 24 Factorial(3) 3 * Factorial(2) 3 * 2 = 6 Factorial(2) 2 * Factorial(1) 2 * 1 = 2 Factorial(1) n <= 1 — стоп 1
Factorial(5) вызывает Factorial(4) и так далее до Factorial(1), где срабатывает условие остановки, а потом ответы 1, 2, 6, 24 и 120 возвращаются вверх по цепочке.

Методы в программе без Main

В программе на операторах верхнего уровня методы пишут после основного кода:

C#
Console.WriteLine(Square(7));
Console.WriteLine(Square(12));

int Square(int x)
{
    return x * x;
}
Вывод
49
144

Такие методы называются локальными функциями. Работают они так же, но с ограничением: два метода с одним именем (перегрузка) здесь не получатся. Если нужна перегрузка, пишите классическую форму с классом.

Частые ошибки

Коротко

Проверьте себя

1. Что значит void в заголовке метода?
2. Что выведет программа?
C#
class Program
{
    static void Main()
    {
        int a = 1;
        Change(a);
        Console.WriteLine(a);
    }

    static void Change(int x)
    {
        x = 100;
    }
}
3. Можно ли в одном классе объявить static int F(int x) и static int F(int x, int y)?
4. Почему без условия if (n <= 1) return 1; рекурсивный факториал не работает?