Методы в C#: как объявить, вызвать и передать параметры

Методы в C#: как объявить, вызвать и передать параметры Полезное

Метод в C# — это именованный блок кода внутри класса или структуры, который выполняет одну задачу и при необходимости возвращает результат. Метод объявляют один раз, а вызывают сколько угодно раз, передавая ему входные данные через параметры.

Ниже разберем, из чего состоит объявление метода, чем отличаются модификаторы доступа, как передаются параметры (по значению, ref, out, in, params), что такое необязательные и именованные аргументы, перегрузка, статические и экземплярные методы, а также короткая запись через expression-bodied и локальные функции.

Из чего состоит объявление метода

Минимальный рабочий пример, который можно скопировать и запустить:

using System;

class Program
{
    // модификаторы | тип результата | имя | параметры
    static int Sum(int a, int b)
    {
        return a + b; // тело метода
    }

    static void Main()
    {
        int result = Sum(2, 3);
        Console.WriteLine(result); // выведет 5
    }
}

Объявление читается слева направо: сначала модификаторы (static), затем тип возвращаемого значения (int), имя метода (Sum), список параметров в круглых скобках и тело в фигурных скобках. Вызов — это имя метода и фактические аргументы: Sum(2, 3).

Тип void означает, что метод ничего не возвращает. Точка входа программы Main — это обычный метод с зарезервированным именем; его может вызвать среда выполнения при старте.

Модификаторы доступа

Модификатор доступа задает, откуда виден член класса. Если модификатор не указан, член класса по умолчанию получает private.

  • public — виден отовсюду;
  • private — только внутри своего класса (значение по умолчанию для членов класса);
  • protected — внутри класса и его наследников;
  • internal — внутри той же сборки (проекта);
  • protected internal — внутри сборки или у наследников;
  • private protected — у наследников внутри той же сборки.

Модификатор доступа и static — это разные вещи: первый отвечает за видимость, второй за то, привязан ли метод к объекту или к типу.

Способы передачи параметров

По умолчанию параметр передается по значению: метод получает копию, и изменение параметра не затрагивает исходную переменную. Ключевые слова ref, out и in меняют это поведение, а params позволяет принять переменное число аргументов.

Способ Инициализация до вызова Метод может менять Обязан присвоить внутри Когда применять
по значению не требуется только копию нет обычная передача данных
ref обязательна да нет двусторонний обмен через переменную
out не требуется да, записывает да, на всех путях выхода вернуть несколько значений
in обязательна нет, только чтение нет большая структура без копирования
params не требуется работает с массивом нет переменное число аргументов

Разница между ref, out и in сводится к трем вопросам: нужно ли инициализировать переменную до вызова, может ли метод ее менять и обязан ли он что-то записать. ref — двусторонний и требует готового значения. out — для вывода: значение до вызова не нужно, но метод обязан присвоить его перед выходом. in — только чтение: метод видит переменную по ссылке, но менять не вправе.

Пример ref: переменная должна быть инициализирована заранее.

static void Increment(ref int x)
{
    x++;
}

int n = 5;            // ref требует значения до вызова
Increment(ref n);
Console.WriteLine(n); // выведет 6

Пример out показывает типичную ошибку — не присвоить параметр на всех путях выхода:

static bool TryPositive(string s, out int value)
{
    if (int.TryParse(s, out value) && value > 0)
        return true;
    return false; // value присвоен не везде
}

Компилятор откажется собирать такой код:

error CS0177: выходной параметр "value" должен быть присвоен, прежде чем управление покинет текущий метод

Исправление — присвоить value на ветке, где метод возвращает false:

static bool TryPositive(string s, out int value)
{
    if (int.TryParse(s, out value) && value > 0)
        return true;
    value = 0;    // присваиваем на всех путях выхода
    return false;
}

Параметр in передает данные по ссылке только для чтения — это полезно для больших структур, чтобы не копировать их.

struct Point { public double X; public double Y; }

static double Length(in Point p)
{
    // p доступен только для чтения: строка p.X = 0 не скомпилируется
    return Math.Sqrt(p.X * p.X + p.Y * p.Y);
}

Параметр params принимает переменное число аргументов одного типа и внутри метода работает как массив. Он должен быть последним в списке параметров.

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

Console.WriteLine(SumAll(1, 2, 3, 4)); // выведет 10
Console.WriteLine(SumAll());           // выведет 0

Необязательные и именованные аргументы

Необязательный параметр получает значение по умолчанию прямо в объявлении. Если при вызове аргумент не передан, подставляется значение по умолчанию. Такие параметры идут после обязательных.

Именованные аргументы позволяют указать значение по имени параметра, а не по позиции — это удобно, когда параметров много.

static string Greet(string name, string greeting = "Привет")
{
    return $"{greeting}, {name}!";
}

Console.WriteLine(Greet("Аня"));                        // Привет, Аня!
Console.WriteLine(Greet("Аня", "Здравствуйте"));        // Здравствуйте, Аня!
Console.WriteLine(Greet(greeting: "Хай", name: "Аня")); // именованные аргументы

Перегрузка методов

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

static int Max(int a, int b) => a > b ? a : b;
static double Max(double a, double b) => a > b ? a : b;

Console.WriteLine(Max(3, 7));       // вызовется int-версия, выведет 7
Console.WriteLine(Max(2.5, 1.5));   // вызовется double-версия, выведет 2.5

Статические и экземплярные методы

Экземплярный метод работает с конкретным объектом и имеет доступ к его полям. Статический метод принадлежит типу, а не объекту, и вызывается через имя класса.

class Counter
{
    private int count;         // поле экземпляра
    public static int Total;   // общее поле для всех объектов

    public void Add()          // экземплярный метод
    {
        count++;
        Total++;
    }

    public int Current() => count;
}

var c = new Counter();
c.Add();
Console.WriteLine(c.Current());     // 1, у этого объекта
Console.WriteLine(Counter.Total);   // 1, общий счетчик через имя класса

Ориентир простой: если метод не обращается к данным конкретного объекта, его можно и стоит сделать статическим.

Возвращаемое значение и выход из метода

Если тип результата не void, метод обязан вернуть значение оператором return на всех путях выхода. Оператор return также досрочно прекращает работу метода, в том числе в void-методе (тогда после return значение не пишут).

static void PrintIfPositive(int x)
{
    if (x <= 0)
        return;                       // досрочный выход, дальше код не выполнится
    Console.WriteLine($"Положительное: {x}");
}

Короткая запись: expression-bodied и локальные функции

Если тело метода — одно выражение, его записывают через => (expression-bodied метод). Это тот же метод, просто короче.

public int Square(int x) => x * x;

Локальная функция — это метод, объявленный внутри другого метода. Она видна только в нем и удобна для вспомогательной логики, которую не нужно выносить наружу.

static long Factorial(int n)
{
    return Calc(n);

    long Calc(int k) => k <= 1 ? 1 : k * Calc(k - 1); // локальная функция
}

Console.WriteLine(Factorial(5)); // выведет 120

Выводы

  • Метод в C# — именованный блок кода в классе или структуре; объявляется один раз, вызывается многократно.
  • По умолчанию параметры передаются по значению; ref — двусторонний обмен, out — вернуть значение (обязателен для записи), in — только чтение, params — переменное число аргументов.
  • Перегрузка различает методы по списку параметров, но не по типу возвращаемого значения.
  • Статический метод привязан к типу и вызывается через имя класса, экземплярный — к объекту и его полям.
  • Одно выражение оформляют через =>, вспомогательную логику — локальной функцией внутри метода.

Где применяется / связь с практикой

Методы — это базовый строительный блок любого приложения на C#: от точки входа Main до бизнес-логики сервисов и обработчиков в ASP.NET. Умение выбрать способ передачи параметров и грамотно разбить код на методы напрямую влияет на читаемость и сопровождаемость проекта.

Освойте тему на практике

Разобрать методы, классы и синтаксис C# на практике под руководством преподавателя можно на курсе C# Basic. Если хочется сначала посмотреть формат и уровень, загляните в расписание открытых уроков Otus — там регулярно проходят бесплатные занятия по C# и .NET.

FAQ

Можно ли объявить метод вне класса?
В обычном коде нет: метод принадлежит классу, структуре или другому типу. Исключение — top-level statements в файле с точкой входа, где компилятор сам оборачивает код в скрытый класс.

Чем метод отличается от функции?
В C# это фактически синонимы: функцию, объявленную внутри типа, называют методом. Термин «локальная функция» подчеркивает лишь то, что она объявлена внутри другого метода.

Сколько значений может вернуть метод через return?
Через return — одно. Чтобы вернуть несколько, используют параметры out, либо возвращают кортеж вида (int, int), либо отдельный объект или структуру.

OTUS Журнал
Скидка 5% 12-19 октября на курсы (popup)
Бесплатные открытые уроки (поп-ап)