Метод это именованный блок кода, который принимает входные данные (параметры), выполняет свою работу и может вернуть результат (возвращаемое значение). В C# каждый метод находится внутри какого-либо типа; свободных функций нет.
Вывод:
Headphones 48.00
Cable 11.40
Устройство метода
public static decimal PriceWithTax(decimal net, decimal taxRate)
{
return net * (1 + taxRate);
}
- Модификатор доступа (
public,private, ...): кто может его вызывать. Если он опущен, методprivateдля своего типа. static: метод принадлежит типу, а не объекту. Подробнее ниже.- Возвращаемый тип: тип возвращаемого значения или
void, если значения нет. - Имя: по соглашению в PascalCase, обычно глагол (
Calculate,SendEmail,TryParse). - Список параметров: ноль или больше пар
type name. Каждый параметр это локальная переменная, инициализированная аргументом вызывающего кода. - Тело: операторы между фигурными скобками.
Имя плюс типы параметров (и модификаторы ref, out или in, если есть) образуют сигнатуру метода. Возвращаемый тип в неё не входит, и это важно для перегрузки.
Возвращаемые значения и void
return сразу завершает метод и возвращает значение. Метод с возвращаемым типом должен возвращать значение этого типа на каждом пути выполнения:
static string Grade(int score)
{
if (score >= 50)
return "pass";
} // error CS0161: 'Program.Grade(int)': not all code paths return a value
Компилятор проверяет каждую ветку. Добавьте недостающий return "fail"; или throw, если попадание в эту точку это ошибка.
Метод void ничего не возвращает. Он всё равно может использовать голый return;, чтобы выйти раньше:
Вывод:
ana@mail.com: newsletter sent
ben@mail.com: not subscribed, skipped
False
True
Методы с телом-выражением
Когда тело метода состоит из одного выражения, => заменяет фигурные скобки и return:
static decimal Discount(decimal price) => price * 0.1m;
static bool IsAdult(int age) => age >= 18;
static void Log(string message) => Console.WriteLine($"[log] {message}");
Это тот же метод, записанный короче. Такая форма работает и для методов void, если тело состоит из одного выражения (вызова, присваивания).
Параметры это копии
По умолчанию аргументы передаются по значению: параметр получает копию аргумента. Присваивание параметру внутри метода не затрагивает переменную вызывающего кода:
Вывод:
inside: 42
after: 21
scores: 70, 85, 100
List изменился, потому что для ссылочного типа копируемое значение это ссылка: метод и вызывающий код указывают на один и тот же объект списка. Переприсваивание scores = new List<int>() внутри метода не затронуло бы вызывающий код. Чтобы метод мог присвоить значение переменной вызывающего кода, используйте ref или out; чтобы задать параметрам значения по умолчанию и передавать их по имени, используйте необязательные параметры и именованные аргументы.
Возврат нескольких значений через кортеж
Начиная с C# 7.0 метод может вернуть кортеж с именованными элементами, а вызывающий код может деконструировать его в отдельные переменные:
Вывод:
min 15, max 27, avg 21.0
range 12
_ отбрасывает ненужный элемент. Для результатов, которые используются во многих местах, небольшой класс или запись с именованными свойствами описывают форму лучше, чем кортеж.
Перегрузка
Несколько методов могут иметь одно имя, если их списки параметров различаются количеством, типами или порядком типов. Компилятор выбирает наилучшее совпадение для каждого вызова:
Вывод:
12.57
13.50
25
Area(5) выбирает версию с int, потому что это точное совпадение; Area(2.0) выбирает версию с double. Методы не могут различаться только возвращаемым типом: два метода Area(int), возвращающие int и double, дают ошибку CS0111, Type 'Program' already defines a member called 'Area' with the same parameter types, потому что по вызову вроде Area(5) их нельзя было бы различить.
Вызов, который не принимает ни одна перегрузка, тоже не компилируется: Area(1, 2, 3) это CS1501, No overload for method 'Area' takes 3 arguments, а Area("big") это CS1503, Argument 1: cannot convert from 'string' to 'double'.
params: переменное число аргументов
Параметр-массив params принимает любое число аргументов, включая ноль. Компилятор сам создаёт массив:
Вывод:
empty cart: 0 item(s), 0
one item: 1 item(s), 9.99
three items: 3 item(s), 19.75
from array: 2 item(s), 50
Правила: params должен быть последним параметром (иначе CS0231), в методе может быть не больше одного такого параметра, а переданный существующий массив передаётся как есть. Console.WriteLine("{0} and {1}", a, b) и string.Format это методы с params object[]. C# 13 расширил params на другие типы коллекций, например List<T> и ReadOnlySpan<T>; до этого допускались только массивы.
Статические и экземплярные методы
Метод static принадлежит типу и вызывается через имя типа: Math.Max(3, 7), Program.Total(...). Экземплярный метод принадлежит объекту и может использовать поля этого объекта:
Вывод:
12
Вызов экземплярного метода из статического без объекта это самая частая ошибка новичков в C#:
class Program
{
void Greet() => Console.WriteLine("hi");
static void Main()
{
Greet();
// error CS0120: An object reference is required for the non-static field, method, or property 'Program.Greet()'
}
}
Пометьте Greet как static или создайте объект и вызовите метод на нём: new Program().Greet();. Статические члены и статические классы подробнее описаны на странице о static.
Метод Main
С Main начинается программа. Он должен быть static, и допустимы следующие сигнатуры:
static void Main() { }
static void Main(string[] args) { } // command-line arguments
static int Main(string[] args) { return 0; } // exit code for the shell
static async Task Main() { } // C# 7.1 and later
Форма async Task Main позволяет точке входа напрямую использовать await (см. async и await). Начиная с C# 9 файл может вместо этого содержать операторы верхнего уровня: код вообще без класса и Main, как в новом проекте dotnet new console. Компилятор генерирует вокруг него класс и метод Main, так что это то же самое, записанное короче; args по-прежнему доступен.
Рекурсия
Метод может вызывать сам себя. Каждый вызов получает собственные параметры и локальные переменные, а базовый случай должен останавливать цепочку:
Вывод:
120
2432902008176640000
35
Без базового случая или при слишком глубоких входных данных каждый вызов добавляет кадр стека, пока программа не завершится с StackOverflowException. Это исключение нельзя перехватить, поэтому для входных данных неизвестной глубины переписывайте рекурсию в виде цикла.
Локальные функции
C# 7.0 ввёл локальные функции: методы, объявленные внутри другого метода и видимые только там. Они могут использовать переменные объемлющего метода:
// C# 7.0 and later
static int CountValid(string[] codes)
{
int count = 0;
foreach (string code in codes)
{
if (IsValid(code)) count++;
}
return count;
bool IsValid(string c) => c.Length == 6 && char.IsLetter(c[0]);
}
Они держат вспомогательный метод рядом с единственным местом вызова, не открывая его остальному классу. Начиная с C# 8 их можно пометить static, чтобы запретить захват внешних переменных. Где локальная функция недоступна, ту же работу выполняет закрытый статический метод в том же классе, как в остальных примерах на этой странице.
Часто задаваемые вопросы
Чем метод отличается от функции в C#?
В C# каждая функция объявляется внутри класса, структуры или записи и называется методом. Свободных функций нет. Даже операторы верхнего уровня (C# 9) и локальные функции (C# 7) компилируются в методы сгенерированного или объемлющего типа. Слово «функция» используют неформально, а также для лямбд и делегатов.
Что делает params в C#?
params позволяет методу принимать переменное число аргументов одного типа: static int Sum(params int[] numbers) можно вызвать как Sum(), Sum(4), Sum(4, 5, 6) или Sum(existingArray). Компилятор упаковывает аргументы в массив. Параметр params должен быть последним, и в методе он может быть только один.
Что такое метод Main в C#?
Main это точка входа программы: метод, который среда выполнения вызывает первым. Он должен быть static, может возвращать void или int (код завершения) и может принимать string[] args с аргументами командной строки. Начиная с C# 7.1 он может быть async Task Main, а начиная с C# 9 операторы верхнего уровня позволяют компилятору сгенерировать его за вас.
Как вернуть несколько значений из метода в C#?
Верните кортеж: static (int Min, int Max) Range(int[] values) и return (min, max);. Вызывающий код может читать result.Min или деконструировать через var (lo, hi) = Range(values);. Для значений, которые путешествуют вместе по всему коду, понятнее небольшой класс или запись; более старая альтернатива это параметры out.
Почему возникает ошибка «An object reference is required for the non-static field, method, or property»?
Статический метод, например Main, напрямую вызвал экземплярный метод. Статические методы принадлежат типу, и у них нет объекта, на котором можно вызвать такой метод. Либо пометьте вызываемый метод как static, либо сначала создайте объект: var app = new Program(); app.Greet();.
Что означает «not all code paths return a value»?
Ошибка CS0161: у метода с возвращаемым типом есть хотя бы один путь, который доходит до закрывающей скобки без return. Обычно if возвращает значение, но после него нет ни else, ни завершающего return. Добавьте return (или throw) в конце метода.