$ sudo teach IT
Модуль 8 · Функции

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

Одно имя — разные параметры. Как компилятор выбирает нужную версию.

Представь пульт от телевизора. Кнопка «+» меняет громкость, но если нажать и держать — меняет канал. Одна и та же кнопка, но с разными параметрами нажатия делает разное. В C# это называется перегрузка методов (method overloading): несколько методов с одним именем, но разными наборами параметров.

❓

Что такое перегрузка методов?

Перегрузка (overload) — это когда ты объявляешь несколько методов с одинаковым именем, но разными параметрами (количество, типы или порядок). Компилятор сам выбирает, какой метод вызвать, глядя на аргументы.

Проще говоря: одно имя — много реализаций. Как глагол «идти»: «идти пешком», «идти на автобусе», «идти в кино» — один глагол, разные уточнения.

C# · Простейшая перегрузка
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. По количеству параметров

C# · Перегрузка по количеству
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. По типам параметров

C# · Перегрузка по типам
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. По порядку (разные последовательности типов)

C# · Перегрузка по порядку
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, "Борис");   // вызов второй версии

// Компилятор смотрит на типы аргументов и их порядок
🤔

Как компилятор выбирает перегрузку?

Когда ты вызываешь метод, компилятор выполняет следующие шаги:

  1. Ищет методы с совпадающим именем в текущем классе
  2. Проверяет количество параметров — сколько аргументов передано
  3. Проверяет типы аргументов — каждый аргумент должен быть совместим с типом параметра
  4. Если подходит несколько — выбирает «наиболее конкретную» версию (ближайший тип)
  5. Если не подходит ни одна — ошибка компиляции
  6. Если подходит несколько одинаково — неоднозначность, ошибка компиляции
C# · Как выбирается перегрузка
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 подходит лучше.

C# · Неоднозначность
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!

Распространённый вопрос: можно ли перегрузить метод, меняя только тип возврата? Нет. Компилятор не может определить, какую версию вызывать, только по возвращаемому типу, потому что результат может быть не использован:

C# · ❌ Нельзя
// ❌ Ошибка компиляции! Одинаковая сигнатура
static int GetValue() { return 42; }
static string GetValue() { return "42"; }

// Компилятор не знает, какой вызвать:
// Console.WriteLine(GetValue());  // Какой из двух?!
// int x = GetValue();  // а так понятно? Нет, всё равно ошибка

Сигнатура метода включает: имя метода + количество, типы и порядок параметров. Тип возврата, модификаторы (static, ref) и имена параметров не входят в сигнатуру.

Входит в сигнатуру (можно менять)
НЕ входит (нельзя перегрузить)
Количество параметров
Тип возвращаемого значения
Типы параметров
Модификаторы (static, virtual)
Порядок параметров
Имена параметров
💡 Запомни: сигнатура = имя + (кол-во, типы, порядок параметров). Всё. Если сигнатуры совпадают — перегрузка невозможна, даже если различаются return types.
💡

Когда использовать перегрузку?

Перегрузка — мощный, но не всегда нужный инструмент. Вот правила good practices:

✅ Когда стоит использовать:

  • Альтернативные входные типы: метод принимает int или double или string — одна логика
  • Необязательные параметры через перегрузку (до C# 4.0 это был единственный способ)
  • Улучшение читаемости: Console.WriteLine() имеет 19 перегрузок для всех типов
  • Конструкторы: класс может иметь несколько конструкторов с разными параметрами

❌ Когда лучше не надо:

  • Разная логика: если методы делают совершенно разное — дай им разные имена
  • Слишком много перегрузок (больше 5-6) — запутывает пользователей
  • Вместо значений по умолчанию — сейчас лучше использовать default-параметры

Хороший пример — метод Console.WriteLine который имеет 19 перегрузок. Плохой пример — если бы ты создал 10 версий ProcessData с разными комбинациями, которые делают разное.

🧩

Практический пример: работа с графикой

Создадим класс Vector с перегруженными операциями (на самом деле методами):

C# · Перегрузка на примере
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:

  1. Format(string text) — просто возвращает текст
  2. Format(string text, bool upper) — если upper true, то в верхнем регистре
  3. Format(string text, int width) — центрирует текст по ширине
  4. Format(string text, char pad, int width) — центрирует с заданным символом-заполнителем
  5. Format(int number) — возвращает число с разделителями разрядов
  6. Продемонстрируй все перегрузки в Main
C# · Решение
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));
    }
}
📌

Что важно запомнить

1️⃣
Перегрузка — несколько методов с одним именем, но разными параметрами. Компилятор выбирает подходящий по аргументам вызова.
2️⃣
Способы перегрузки: разное количество параметров, разные типы, разный порядок. Нельзя перегрузить по return type или модификатору.
3️⃣
Сигнатура метода = имя + типы/количество/порядок параметров. Только эти элементы участвуют в различении перегрузок.
4️⃣
Компилятор выбирает точное совпадение. Если точного нет — ищет ближайшее преобразование. Неоднозначность — ошибка.
5️⃣
Перегрузка — для удобства, не для «разной логики». Если методы делают разное — дайте им разные имена. Не злоупотребляйте количеством перегрузок.

Тест: 8.3: Перегрузка методов

3 вопроса

Перегрузка

Premium