Одно и то же действие часто нужно вызывать по-разному: с полным набором данных и с сокращённым. В C# для этого два механизма, и они решают почти одну задачу.
Перегрузка
Несколько методов с одним именем, но разным набором параметров:
static void Log(string message)
{
Console.WriteLine(message);
}
static void Log(string message, string prefix)
{
Console.WriteLine($"[{prefix}] {message}");
}
static void Log(int code)
{
Console.WriteLine($"Код: {code}");
}
Компилятор выбирает подходящий вариант по аргументам вызова:
Log("Запуск"); // первый
Log("Сбой", "ОШИБКА"); // второй
Log(404); // третий
Выбор происходит при компиляции, а не во время работы, – поэтому ошибиться в нём невозможно: если ни один вариант не подходит, программа просто не соберётся.
Вы уже пользовались перегрузкой, не зная об этом. У Console.WriteLine около двух десятков вариантов: для строки, для целого, для дробного, для объекта. Поэтому метод и принимает что угодно, оставаясь строго типизированным.
Тип возврата в различении не участвует. Такая пара не соберётся:
static int Parse(string text) { ... }
static double Parse(string text) { ... } // ошибка: уже есть такой метод
Причина понятна, если посмотреть на вызов Parse("5"); без присваивания: выбрать один из двух
компилятору не по чему.
Необязательные параметры
Часто перегрузки отличаются одним хвостовым аргументом. Тогда проще задать значение по умолчанию:
static void Log(string message, string prefix = "ИНФО")
{
Console.WriteLine($"[{prefix}] {message}");
}
Теперь один метод обслуживает оба случая:
Log("Запуск"); // [ИНФО] Запуск
Log("Сбой", "ОШИБКА"); // [ОШИБКА] Сбой
Правило одно: необязательные параметры идут последними. Иначе при вызове Log("Сбой") было бы
непонятно, какому параметру предназначен единственный аргумент.
static void Log(string prefix = "ИНФО", string message) // ошибка компиляции
Значением по умолчанию может быть только константа, известная при компиляции: число, строка, true, null. Написать DateTime moment = DateTime.Now нельзя – «сейчас» у каждого вызова своё. Обычный обходной приём: поставить null и подставить настоящее значение внутри метода.
Именованные аргументы
Когда параметров много, на месте вызова непонятно, что означает каждый:
CreateUser("Аня", 19, true, false); // что true, а что false?
Аргумент можно передать по имени параметра – и тогда пояснения не нужны:
CreateUser(name: "Аня", age: 19, isAdmin: true, isBlocked: false);
Именованные аргументы разрешено переставлять местами и удобно сочетать с необязательными: можно пропустить середину и задать только последний.
static void Connect(string host, int port = 80, bool secure = false, int timeout = 30) { }
Connect("example.com", timeout: 5); // порт и secure остались по умолчанию
Логический аргумент на месте вызова почти всегда стоит называть по имени. Save(data, true) заставляет открывать объявление метода, Save(data, overwrite: true) – нет.
Перегрузка или значение по умолчанию
Выбор между ними не только вкусовой.
| Берите перегрузку | Берите значение по умолчанию |
|---|---|
| варианты делают разные вещи | вариант один, отличается мелочью |
| различаются типы параметров | различается только наличие хвостового аргумента |
| метод публичный в библиотеке | метод внутри вашего проекта |
Последняя строка требует пояснения. Значение по умолчанию подставляется на месте вызова, а не внутри метода: компилятор буквально дописывает его в вызывающий код. Если библиотека поменяет умолчание, а вы не пересоберёте свой проект, у вас останется старое значение. Внутри одного проекта, который всегда собирается целиком, эта тонкость не проявляется.
Переменное число аргументов
Когда заранее неизвестно, сколько значений передадут, помогает params:
static int Sum(params int[] numbers)
{
int total = 0;
foreach (var number in numbers)
{
total += number;
}
return total;
}
Вызывать можно как угодно – с любым количеством аргументов или сразу массивом:
Sum(1, 2, 3); // 6
Sum(1, 2, 3, 4, 5); // 15
Sum(); // 0
Sum(new[] { 1, 2, 3 }); // тоже работает
Параметр с params обязан быть последним и в методе может быть только один – иначе непонятно, где
кончается один набор и начинается другой.