CodeGym /Kursy /C# SELF /Metody wirtualne w C#: polimorfizm i nadpisywanie

Metody wirtualne w C#: polimorfizm i nadpisywanie

C# SELF
Poziom 20 , Lekcja 2
Dostępny

1. Wprowadzenie

Wyobraź sobie, że masz bazową klasę Animal z metodą Speak(). Dla wszystkich zwierzaków domyślnie wypisuje "Jakiś dźwięk". Chcesz stworzyć klasy potomne, np. Dog i Cat, żeby mówiły nie "Jakiś dźwięk", tylko swoje prawdziwe kwestie ("Woof!" i "Meow!").

Można by w każdym potomku napisać swoją metodę Speak(), ale jeśli pracujesz z kolekcją typu Animal[], wywołanie animal.Speak() i tak odpali metodę Animal, a nie psa czy kota — bo C# domyślnie patrzy na typ zmiennej, a nie na faktyczny obiekt pod spodem.

Przykład na chłopski rozum:


Animal dog = new Dog();
dog.Speak(); // Ooo, co się wypisze?

Domyślnie — "Jakiś dźwięk", a nie "Woof!", nawet jeśli w Dog jest własna metoda Speak(). Co z tym zrobić? Trzeba zadeklarować metodę jako virtual (w bazowej klasie) i override (w pochodnej).

Co to jest metoda wirtualna?

virtual-metoda — to metoda, która jest zadeklarowana w bazowej klasie z modyfikatorem virtual i jest po to, żeby w potomkach można ją było nadpisać ( override).

Gdy wywołasz taką metodę przez referencję do bazowej klasy, zostanie wywołana metoda faktycznego "żywego" obiektu — jak w życiu: jeśli oswoiłeś zwierzaka i to kot, to powie "Meow!", a nie "Jakiś dźwięk".

Metody wirtualne to podstawa polimorfizmu w C#.

2. Deklarowanie metod wirtualnych

Jak zadeklarować metodę wirtualną

W bazowej klasie deklarujemy metodę ze słowem kluczowym virtual:


public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Jakiś dźwięk");
    }
}
Deklaracja metody wirtualnej w bazowej klasie

W potomku używamy słowa kluczowego override:


public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Woof!");
    }
}

public class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Meow!");
    }
}
Nadpisywanie metody wirtualnej w potomkach

Przykład: pokaz polimorfizmu


// Na przykład w pliku Program.cs

// Bazowa klasa Animal z wirtualną metodą Speak
public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Jakiś dźwięk");
    }
}

// Klasa Dog, nadpisuje zachowanie Speak
public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Woof!");
    }
}

// Klasa Cat, też nadpisuje Speak
public class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Meow!");
    }
}

public static class Program
{
    public static void Main()
    {
        Animal[] animals = new Animal[]
        {
            new Animal(),
            new Dog(),
            new Cat()
        };

        foreach (Animal animal in animals)
        {
            animal.Speak(); // Wypisze: "Jakiś dźwięk", "Woof!", "Meow!"
        }
    }
}

Widzisz, jaka magia w metodzie wirtualnej? Nawet jeśli zmienna jest typu Animal, wywołuje się wersja metody z konkretnej implementacji.

3. Co się dzieje "pod maską": wirtualna tablica

Krótko o mechanizmie

Gdy deklarujesz metodę jako wirtualną, kompilator dorzuca ją do specjalnej "wirtualnej tablicy" metod dla każdego typu (prawie jak menu w stołówce dla każdego dania). Gdy wywołujesz taką metodę, C# patrzy nie na typ zmiennej, tylko na to, co faktycznie jest pod referencją — i "podstawia" tę metodę, która odpowiada faktycznemu typowi obiektu.

Można powiedzieć, że C# sam podkłada właściwy przepis zamiast uniwersalnego, jeśli klasa ma swoją wersję.

Analogicznie: umowa z możliwością "dopisać punkt drobnym drukiem"

Wyobraź sobie, że masz umowę najmu (bazowa klasa), i jest tam napisane, że najemca płaci domyślnie stałą kwotę co miesiąc (metoda PayRent()). Ale ty — cwany właściciel — pozwalasz w załącznikach do umowy wpisać specjalne warunki (metoda wirtualna). Jeśli któryś z najemców (potomek) skorzysta z tego prawa, napisze swój override i teraz opłata jest pobierana po swojemu.

4. Kiedy warto używać metod wirtualnych?

  • Jeśli metoda w bazowej klasie powinna działać typowo dla większości potomków, ale niektóre mogą potrzebować własnego zachowania.
  • Kiedy budujesz hierarchię klas, gdzie przewidujesz rozszerzanie lub zmianę części logiki.
  • Kiedy tworzysz elastyczną architekturę, np. dla logiki biznesowej, gdzie różne typy operacji mogą wymagać indywidualnego zachowania.

W realnych projektach metody wirtualne to podstawa wzorców Metoda szablonowa (Template Method), Strategia (Strategy), no i w ogóle połowy OOP.

Porównanie ze zwykłymi metodami

Zwykła metoda Metoda wirtualna
Można nadpisywać Nie Tak (override)
Jak się wywołuje Po typie zmiennej Po "prawdziwym" typie obiektu
Polimorfizm Nie Tak

Najczęściej zadawane pytania

  • Czy można zrobić konstruktor wirtualnym?
    Nie! Konstruktory nigdy nie są wirtualne — zawsze działają ściśle według typu obiektu.
  • Czy metody wirtualne mogą być statyczne?
    Nie, tylko metody instancyjne mogą być wirtualne. Statyczne nie biorą udziału w polimorfizmie, bo nie ma obiektu — a kto miałby nadpisywać?
  • Czy pola mogą być wirtualne?
    Nie, tylko metody, właściwości i zdarzenia.

5. Praktyka: rozwijamy aplikację "Świat zwierząt"

W ramach naszej naukowej apki już zrobiłeś prostą hierarchię zwierzaków. Dodajmy im nowe metody wirtualne, żeby różne zwierzaki mogły jeść po swojemu!

Przykład kodu z komentarzami


// W bazowej klasie ustawiamy wirtualną metodę Eat
public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Jakiś dźwięk");
    }

    public virtual void Eat()
    {
        Console.WriteLine("Je zwykłe jedzenie.");
    }
}

// W Dog nadpisujemy Eat
public class Dog : Animal
{
    public override void Speak() => Console.WriteLine("Woof!");
    public override void Eat() => Console.WriteLine("Je psie jedzenie.");
}

// W Cat też swoje zachowanie
public class Cat : Animal
{
    public override void Speak() => Console.WriteLine("Meow!");
    public override void Eat() => Console.WriteLine("Je ryby.");
}

public static class Program
{
    public static void Main()
    {
        Animal[] animals = new Animal[]
        {
            new Animal(),
            new Dog(),
            new Cat()
        };
        foreach (var animal in animals)
        {
            animal.Speak();
            animal.Eat();
            Console.WriteLine("---");
        }
    }
}

No i gotowe, teraz każdy kotek i piesek je po swojemu! A bazowy Animal dalej wcina coś dziwnego, jak prawdziwy default.

6. Typowe błędy i niuanse

  • Można zapomnieć override w potomku — i wtedy metoda bazowej klasy zostaje na miejscu.
  • Albo odwrotnie, przypadkiem zapomnieć virtual w bazowej klasie, wtedy w potomku nie da się napisać override.

Nie da się napisać override bez virtual

Kompilator C# nie pozwoli nadpisać metody, która nie była zadeklarowana jako wirtualna (albo abstrakcyjna) w bazowej klasie.

Po co jest override

override jasno mówi kompilatorowi i czytelnikowi kodu: "Nie piszę po prostu nowej metody — świadomie zmieniam to, co jest w bazowej klasie". To zapobiega błędom, gdy chcesz "nadpisać", ale przypadkiem piszesz nową metodę o podobnej sygnaturze.

Jeśli zapomniałeś override i napisałeś nową metodę

public class Dog : Animal
{
    public void Speak()
    {
        Console.WriteLine("Woof!");
    }
}
To NIE jest nadpisanie! Polimorfizm nie działa.

Ale to nie jest nadpisanie! Gdy odwołasz się do Dog przez zmienną typu Animal, wywoła się metoda zwierzaka ("Jakiś dźwięk"), a nie psa. O, te niespodzianki z typami!

2
Zadanie
C# SELF, poziom 20, lekcja 2
Niedostępne
Tworzenie klasy z wirtualną metodą
Tworzenie klasy z wirtualną metodą
2
Zadanie
C# SELF, poziom 20, lekcja 2
Niedostępne
Wirtualna metoda z dodatkową logiką
Wirtualna metoda z dodatkową logiką
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION