JavaRush /Курси /C# SELF /Перевантаження методів (Method Overloading)

Перевантаження методів (Method Overloading)

C# SELF
Рівень 21 , Лекція 1
Відкрита

1. Вступ

У реальному житті багато дій — наче багатофункціональні швейцарські ножі: одна й та сама команда може працювати з різними наборами інструментів. Наприклад, уявіть собі банкомат: якщо ви вставили картку — банкомат запитує PIN‑код; якщо ввели номер телефону — очікує код підтвердження з SMS. Дія одна — «перевірити користувача», але способи різні.

У програмуванні ми часто стикаємося зі схожою ситуацією: потрібно виконати, здавалося б, одну операцію, але дані можуть бути різних типів або з різною кількістю параметрів. Наприклад, наш метод має виводити привітання як для людини, так і для тварини, або підсумовувати два, три чи навіть десять цілих чисел.

Звісно, можна назвати методи по‑різному: SumTwo, SumThree, SumArray. Але програмісти — люди ліниві (недарма кажуть, що лінь — рушій прогресу). До того ж так код стане менш читабельним.

Перевантаження методу

Перевантаження методів — це спосіб «змусити» один і той самий метод працювати з різними наборами параметрів, причому імʼя методу не змінюється. Це одна з форм поліморфізму, але не пов’язана зі спадкуванням.

Перевантаження методу — це можливість створювати в одному класі (або структурі) кілька методів з однаковим імʼям, але з різними списками параметрів (за типом, кількістю та/або порядком).

Сигнатура методу

Сигнатура методу в C# — це його імʼя плюс тип(и) і порядок параметрів. Тип, що повертається, не входить до сигнатури! Це часто призводить до неочікуваних помилок (про них розкажу трохи пізніше).

2. Перевантаження в дії: прості приклади

Давайте створимо клас Greeter, який вітатиме користувачів по‑різному: просто за імʼям, за імʼям і віком або взагалі без параметрів.


public class Greeter
{
    // Привітання без параметрів
    public void Greet()
    {
        Console.WriteLine("Привіт, світ!");
    }

    // Привітання з імʼям
    public void Greet(string name)
    {
        Console.WriteLine($"Привіт, {name}!");
    }

    // Привітання з імʼям і віком
    public void Greet(string name, int age)
    {
        Console.WriteLine($"Привіт, {name}! Тобі вже {age} років? Непогано!");
    }
}

Тепер можна викликати будь‑який з цих методів, і компілятор C# сам вибере потрібний варіант — зважаючи на кількість і типи переданих параметрів.

var greeter = new Greeter();
greeter.Greet();                // Привіт, світ!
greeter.Greet("Аня");           // Привіт, Аня!
greeter.Greet("Петро", 23);     // Привіт, Петро! Тобі вже 23 років? Непогано!

3. Відмінність за типом і кількістю параметрів

Перевантаження працює, якщо методи відрізняються:

  • кількістю параметрів,
  • типом принаймні одного параметра,
  • порядком типів параметрів (але тут варто бути обережнішими).

Спробуймо додати ще одну перевантажену версію, яка приймає лише вік:


public void Greet(int age)
{
    Console.WriteLine($"Стільки років — це круто! ({age} років)");
}

Тепер виклики:

greeter.Greet(10);          // Стільки років — це круто! (10 років)

Важливо памʼятати: якщо методи відрізняються лише типом, що повертається, перевантажити їх не можна. Наприклад, такий код спричинить помилку:


// Помилка компіляції!
public int Foo(string s) { ... }
public double Foo(string s) { ... }
Перевантаження тільки за типом, що повертається, неможливе!

Компілятор «сваритиметься»: «Вже визначено метод Foo(string), давайте щось складніше!»

4. Перевантаження і стандартна бібліотека C#

Перевантаження — це не лише наш вигаданий Greet. Відкрийте документацію .NET для Console.WriteLine:

Сигнатура Призначення
WriteLine()
Друкує порожній рядок
WriteLine(string)
Друкує рядок
WriteLine(int)
Друкує ціле число
WriteLine(double)
Друкує дробове число
WriteLine(string, object)
Форматує рядок з одним аргументом
WriteLine(string, params object[])
Форматує рядок із кількома аргументами

Це все — перевантаження одного й того самого методу — WriteLine. Тепер ви розумієте, чому завжди можете писати:

Console.WriteLine("Просто рядок");
Console.WriteLine(123);
Console.WriteLine(2.5);
Console.WriteLine("Сума: {0}", 42);

І компілятор щоразу трактує ваш виклик коректно!

5. Як компілятор обирає, яке перевантаження викликати?

Тут усе суворо: він зважає на типи та кількість фактично переданих аргументів. Невелика таблиця для наочності:

Виклик Яка версія спрацює?
greeter.Greet()
void Greet()
greeter.Greet(75)
void Greet(int)

Що буде при неоднозначності?

Іноді ситуація виходить з‑під контролю. Ось приклад неоднозначного перевантаження — компілятор не зможе вибрати потрібну версію:


public void Print(int a, double b) { ... }
public void Print(double a, int b) { ... }

printer.Print(5, 10); 
// Помилка: неоднозначність — яке Print викликати? (обидва наче підходять)

Компілятор видасть помилку неоднозначності. У таких випадках краще уникати перевантажень з однаковою кількістю параметрів і близькими типами, коли це може заплутати компілятор.

6. params — змінна кількість параметрів

Припустімо, ви хочете реалізувати метод, який приймає невизначену кількість чисел. Вам у пригоді стане ключове слово params.


public void SumAll(params int[] numbers)
{
    int sum = 0;
    foreach (int n in numbers)
        sum += n;
    Console.WriteLine($"Сума: {sum}");
}
Метод зі змінною кількістю параметрів ( params)

Тепер ви можете викликати:

SumAll(1, 2, 3);           // Сума: 6
SumAll(10, 20);            // Сума: 30
SumAll();                  // Сума: 0

Методи з params можна комбінувати з перевантаженням, але головне — не створювати таких перевантажень, які заважатимуть компілятору однозначно зрозуміти, яку версію методу ви мали на увазі.

7. Перевантаження і модифікатори параметрів (ref, out, in)

C# розрізняє методи за модифікаторами параметрів (тобто сигнатура void Foo(int a) відрізняється від void Foo(ref int a), і обидва можуть існувати в одному класі):

public void SetValue(int a)
{
    a = 42;
}

public void SetValue(ref int a)
{
    a = 100;
}
Перевантаження за модифікатором ref

Виклик без ref потрапить у першу версію, з ref — у другу:

int n = 5;
SetValue(n);     // n залишається 5 (копіюється значення)
SetValue(ref n); // n стає 100

8. Схема: що таке перевантаження


      +----------+
      |  MyClass |
      +----------+
           |
           |            (фрагмент методів)
    +-----------------------+
    |   void Foo()          |
    |   void Foo(int a)     |
    |   void Foo(string s)  |
    |   void Foo(int a, int b) |
    +-----------------------+
Схема перевантаження методів у класі

А якщо уявити це в коді:

// Викликаємо перевантажені версії методу Foo():
var mc = new MyClass();
mc.Foo();              // void Foo()
mc.Foo(5);             // void Foo(int)
mc.Foo("Hello");       // void Foo(string)
mc.Foo(2, 3);          // void Foo(int, int)

9. Приклад: перевантажимо методи у нашому застосунку

Продовжимо розвивати наш навчальний застосунок, додавши перевантаження методу в ієрархію тварин.


public class Animal
{
    public string Name { get; set; }

    // Метод для видавання звуку
    public virtual void MakeSound()
    {
        Console.WriteLine("Якийсь незрозумілий звук...");
    }

    // Перевантажений метод: звук із вказаною гучністю
    public void MakeSound(int volume)
    {
        Console.WriteLine($"Тварина видає звук гучністю {volume} дБ.");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Гав!");
    }

    // Перевантажений метод: гавкіт із гучністю
    public void MakeSound(int volume)
    {
        Console.WriteLine($"Гав! (гучність: {volume} дБ)");
    }
}

Спробуйте такі виклики:

Dog rex = new Dog();
rex.MakeSound();           // Гав!
rex.MakeSound(75);         // Гав! (гучність: 75 дБ)

Зверніть увагу: у класі‑нащадку (Dog) ми перевантажили метод MakeSound(int volume), і тепер у ньому є обидві версії: з параметром і без.

10. Типові помилки при перевантаженні методів

Помилка № 1: спроба перевантажити метод лише за типом, що повертається.
Це неможливо — тип, що повертається, не входить до сигнатури методу. Перевантаження має відрізнятися за кількістю або типами вхідних параметрів, а не за void чи int.

Помилка № 2: неоднозначні перевантаження, які збивають компілятор із пантелику.
Перевантаження з однаковою кількістю параметрів і близькими типами (наприклад, int і double) можуть заплутати компілятор. Приклад: Print(int a, double b) і Print(double a, int b) — виклик Print(1, 1) спричинить помилку неоднозначності.

Помилка № 3: конфлікт params з іншими перевантаженнями.
Метод з params може перехопити виклик, призначений для іншого перевантаження. Якщо типи збігаються, компілятор може вибрати не той метод, який ви очікували.

Помилка № 4: забули, що ref і out входять до сигнатури.
Методи Do(ref int x) і Do(out int x) вважаються різними перевантаженнями. Якщо не враховувати це, легко заплутатися й викликати неправильну версію методу.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ