Урок 8.3 — Перегрузка методов
Одно имя — разные параметры. Как компилятор выбирает нужную версию.
Представь пульт от телевизора. Кнопка «+» меняет громкость, но если нажать и держать — меняет канал. Одна и та же кнопка, но с разными параметрами нажатия делает разное. В C# это называется перегрузка методов (method overloading): несколько методов с одним именем, но разными наборами параметров.
Что такое перегрузка методов?
Перегрузка (overload) — это когда ты объявляешь несколько методов с одинаковым именем, но разными параметрами (количество, типы или порядок). Компилятор сам выбирает, какой метод вызвать, глядя на аргументы.
Проще говоря: одно имя — много реализаций. Как глагол «идти»: «идти пешком», «идти на автобусе», «идти в кино» — один глагол, разные уточнения.
static void Print(int value)
{
Console.WriteLine($"int: {value}");
}
static void Print(string value)
{
Console.WriteLine($"string: {value}");
}
static void Print(double value)
{
Console.WriteLine($"double: {value}");
}
// Компилятор выбирает по типу аргумента:
Print(42); // int: 42
Print("привет"); // string: привет
Print(3.14); // double: 3.14
Способы перегрузки
Перегрузить метод можно тремя способами:
1. По количеству параметров
static int Sum(int a, int b)
{
return a + b;
}
static int Sum(int a, int b, int c)
{
return a + b + c;
}
static int Sum(int a, int b, int c, int d)
{
return a + b + c + d;
}
Console.WriteLine(Sum(2, 3)); // 5
Console.WriteLine(Sum(2, 3, 4)); // 9
Console.WriteLine(Sum(2, 3, 4, 5)); // 14
2. По типам параметров
static string Concat(string a, string b)
{
return a + b; // склеивание строк
}
static int Concat(int a, int b)
{
return a + b; // сложение чисел
}
static string Concat(string a, string b, string c)
{
return a + b + c;
}
Console.WriteLine(Concat("Привет, ", "мир!")); // Привет, мир!
Console.WriteLine(Concat(5, 3)); // 8
Console.WriteLine(Concat("A", "B", "C")); // ABC
3. По порядку (разные последовательности типов)
static void Display(string name, int age)
{
Console.WriteLine($"{name}, {age} лет");
}
static void Display(int age, string name)
{
Console.WriteLine($"Возраст {age}, имя {name}");
}
Display("Анна", 25); // вызов первой версии
Display(30, "Борис"); // вызов второй версии
// Компилятор смотрит на типы аргументов и их порядок
Как компилятор выбирает перегрузку?
Когда ты вызываешь метод, компилятор выполняет следующие шаги:
- Ищет методы с совпадающим именем в текущем классе
- Проверяет количество параметров — сколько аргументов передано
- Проверяет типы аргументов — каждый аргумент должен быть совместим с типом параметра
- Если подходит несколько — выбирает «наиболее конкретную» версию (ближайший тип)
- Если не подходит ни одна — ошибка компиляции
- Если подходит несколько одинаково — неоднозначность, ошибка компиляции
static void Foo(int x)
{
Console.WriteLine("int");
}
static void Foo(double x)
{
Console.WriteLine("double");
}
static void Foo(long x)
{
Console.WriteLine("long");
}
Foo(5); // int — точное совпадение
Foo(5.0); // double — точное совпадение
Foo(5L); // long — точное совпадение
// Foo(5.0f); // float — нет точного, но C# расширит до double
⚠️ Неоднозначность: если компилятор не может выбрать между двумя перегрузками, возникает ошибка. Например, Foo(5) при наличии Foo(int a, double b) и Foo(double a, int b). Компилятор не знает, какой из двух 5 подходит лучше.
static void Bar(int a, double b) { }
static void Bar(double a, int b) { }
// Bar(1, 2); // ❌ Ошибка! Неоднозначность: оба подходят
// Нужно указать типы явно:
Bar(1, 2.0); // ОК — первый int, второй double
Bar(1.0, 2); // ОК — первый double, второй int
Нельзя перегрузить по return type!
Распространённый вопрос: можно ли перегрузить метод, меняя только тип возврата? Нет. Компилятор не может определить, какую версию вызывать, только по возвращаемому типу, потому что результат может быть не использован:
// ❌ Ошибка компиляции! Одинаковая сигнатура
static int GetValue() { return 42; }
static string GetValue() { return "42"; }
// Компилятор не знает, какой вызвать:
// Console.WriteLine(GetValue()); // Какой из двух?!
// int x = GetValue(); // а так понятно? Нет, всё равно ошибка
Сигнатура метода включает: имя метода + количество, типы и порядок параметров. Тип возврата, модификаторы (static, ref) и имена параметров не входят в сигнатуру.
Когда использовать перегрузку?
Перегрузка — мощный, но не всегда нужный инструмент. Вот правила good practices:
✅ Когда стоит использовать:
- Альтернативные входные типы: метод принимает int или double или string — одна логика
- Необязательные параметры через перегрузку (до C# 4.0 это был единственный способ)
- Улучшение читаемости:
Console.WriteLine()имеет 19 перегрузок для всех типов - Конструкторы: класс может иметь несколько конструкторов с разными параметрами
❌ Когда лучше не надо:
- Разная логика: если методы делают совершенно разное — дай им разные имена
- Слишком много перегрузок (больше 5-6) — запутывает пользователей
- Вместо значений по умолчанию — сейчас лучше использовать default-параметры
Хороший пример — метод Console.WriteLine который имеет 19 перегрузок. Плохой пример — если бы ты создал 10 версий ProcessData с разными комбинациями, которые делают разное.
Практический пример: работа с графикой
Создадим класс Vector с перегруженными операциями (на самом деле методами):
using System;
class Program
{
// Перегрузка Draw — рисует разные фигуры
static void Draw(string text)
{
Console.WriteLine($"[{text}]");
}
static void Draw(char symbol, int count)
{
Console.WriteLine(new string(symbol, count));
}
static void Draw(int width, int height, char fill = '#')
{
for (int y = 0; y < height; y++)
{
Console.WriteLine(new string(fill, width));
}
}
// Перегрузка Calc — разные способы расчёта
static double Calc(double a, double b, char op)
{
return op switch
{
'+' => a + b,
'-' => a - b,
'*' => a * b,
'/' => a / b,
_ => throw new ArgumentException("Неизвестная операция")
};
}
static double Calc(double a, double b)
{
return a * b; // по умолчанию — умножение
}
static double Calc(double a)
{
return a * a; // квадрат числа
}
static void Main()
{
Draw("Привет"); // [Привет]
Draw('*', 20); // **********
Draw(5, 3); // прямоугольник 5x3
Draw(7, 2, '.'); // прямоугольник 7x2 из точек
Console.WriteLine();
Console.WriteLine(Calc(5, 3, '+')); // 8
Console.WriteLine(Calc(5, 3)); // 15 (умножение)
Console.WriteLine(Calc(5)); // 25 (квадрат)
}
}
Мини-тест
Вопрос 1
Можно ли перегрузить метод, меняя только тип возврата?
Ответ: Нет. Тип возврата не входит в сигнатуру метода. Компилятор не сможет определить, какую версию вызвать.
Вопрос 2
Какими тремя способами можно перегрузить метод?
Ответ: 1) разное количество параметров, 2) разные типы, 3) разный порядок.
Вопрос 3
Что будет, если два метода имеют одинаковую сигнатуру, но разные тела?
Ответ: Ошибка компиляции. Сигнатуры не должны совпадать.
Вопрос 4
Какая перегрузка будет вызвана: Foo(5) при наличии Foo(int x) и Foo(double x)?
Ответ: Foo(int x) — это точное совпадение. Foo(double x) потребовало бы неявного преобразования.
Задание для тренировки
Создай класс Formatter с перегруженными методами Format:
Format(string text)— просто возвращает текстFormat(string text, bool upper)— если upper true, то в верхнем регистреFormat(string text, int width)— центрирует текст по ширинеFormat(string text, char pad, int width)— центрирует с заданным символом-заполнителемFormat(int number)— возвращает число с разделителями разрядов- Продемонстрируй все перегрузки в Main
using System;
class Program
{
static string Format(string text)
{
return text;
}
static string Format(string text, bool upper)
{
return upper ? text.ToUpper() : text.ToLower();
}
static string Format(string text, int width)
{
if (text.Length >= width)
return text;
int padding = (width - text.Length) / 2;
return new string(' ', padding) + text;
}
static string Format(string text, char pad, int width)
{
if (text.Length >= width)
return text;
int padding = (width - text.Length) / 2;
return new string(pad, padding) + text;
}
static string Format(int number)
{
return number.ToString("N0");
}
static void Main()
{
Console.WriteLine(Format("Привет"));
Console.WriteLine(Format("Привет", true));
Console.WriteLine(Format("Привет", 20));
Console.WriteLine(Format("Привет", '.', 20));
Console.WriteLine(Format(1234567));
}
}
Что важно запомнить
Тест: 8.3: Перегрузка методов
3 вопроса