CodeGym /Kursy /C# SELF /Przeciążanie konstruktorów

Przeciążanie konstruktorów

C# SELF
Poziom 16 , Lekcja 4
Dostępny

1. Wprowadzenie

Załóżmy, że mamy klasę Person:

public class Person
{
    public string Name;
    public int Age;
    
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

I już czujesz się prawie jak geniusz — możesz tworzyć ludzi z dowolnym imieniem i wiekiem. Ale jest problem: czasem informacji jest za mało! Użytkownik aplikacji podał tylko imię, a wiek z jakiegoś powodu poznamy później. Albo chcesz w niektórych przypadkach ustawić "domyślny" wiek. Jasne, można kombinować, ale C# daje na to eleganckie rozwiązanie — przeciążanie konstruktorów.

Przeciążanie konstruktorów oznacza, że jedna klasa może mieć wiele konstruktorów o tej samej nazwie (czyli nazwie klasy), ale z różnymi listami parametrów.

Czym różnią się te listy? Mogą się różnić przez:

  1. Liczbę parametrów: Na przykład jeden konstruktor przyjmuje 2 parametry, inny — 3.
  2. Typy parametrów: Jeden przyjmuje (string, int), inny — (string, decimal).
  3. Kolejność parametrów: (string, int) i (int, string) — to różne sygnatury.

Najważniejsza zasada: kompilator rozróżnia konstruktory (i w ogóle metody) po ich sygnaturze. Sygnatura to nazwa konstruktora (lub metody) i lista jego parametrów (liczba, typy i kolejność). Modyfikatory dostępu (public, private) i, dla zwykłych metod, typ zwracany, nie są częścią sygnatury. Ale konstruktory nie mają typu zwracanego, a nazwa zawsze jest taka jak klasa.

2. Przeciążanie konstruktorów: składnia i przykład


public class Cat
{
    public string Name;
    public string Color;

    // Konstruktor bez parametrów
    public Cat()
    {
        Name = "Bezimienny kot";
        Color = "Szary";
    }

    // Konstruktor z jednym parametrem
    public Cat(string name)
    {
        Name = name;
        Color = "Szary";
    }

    // Konstruktor z dwoma parametrami
    public Cat(string name, string color)
    {
        Name = name;
        Color = color;
    }
}

Użycie:

Cat cat1 = new Cat(); // "Bezimienny kot", "Szary"
Cat cat2 = new Cat("Murzik"); // "Murzik", "Szary"
Cat cat3 = new Cat("Ryżyk", "Rudy"); // "Ryżyk", "Rudy"

Jeśli przy tworzeniu obiektu podajesz parametry, C# "zgadnie" odpowiedni konstruktor po ich liczbie i typie.

To mega wygodne! Użytkownik twojej klasy nie musi podawać parametrów, których nie zna: jeśli chce po prostu kota — bierze konstruktor bez parametrów, jeśli zna imię — używa innego, i tak dalej. Chce mieć pełną kontrolę nad kolorem — bierze trzeci wariant.

3. Jak działa wywołanie przeciążonych konstruktorów?

C# wybiera odpowiedni konstruktor automatycznie na etapie kompilacji, na podstawie typu i liczby przekazanych argumentów. Ciężko się pomylić — jeśli coś się nie zgadza, kompilator od razu cię opieprzy.

Spróbujmy dodać konstruktor z parametrem typu int:

public Cat(int age)
{
    Name = "Bezimienny kot";
    Color = "Szary";
    // Dodatkowy kod dla wieku
}

Teraz możemy stworzyć jeszcze "kociaka po wieku":

Cat oldCat = new Cat(5); // Wywoła się konstruktor Cat(int age)
Wywołanie konstruktora Jaki się wywoła?
new Cat()
Bez parametrów
new Cat("Murka")
Z jednym parametrem typu string
new Cat("Ryżyk", "Rudy")
Z dwoma parametrami typu string
new Cat(10)
Z jednym parametrem typu int

4. Wewnętrzne wywołania konstruktorów: słowo kluczowe this

Często różne konstruktory robią podobną (albo identyczną) robotę. Żeby nie kopiować kodu, można "wywołać jeden konstruktor z drugiego". Do tego służy słowo kluczowe this.


public class Cat
{
    public string Name;
    public string Color;

    public Cat() : this("Bezimienny kot")
    {
        // Ten konstruktor wywołuje Cat(string name)
    }

    public Cat(string name) : this(name, "Szary")
    {
        // Ten konstruktor wywołuje Cat(string name, string color)
    }

    public Cat(string name, string color)
    {
        Name = name;
        Color = color;
    }
}

W efekcie:

  • new Cat() wywołuje Cat(string name), który wywołuje Cat(string name, string color).
  • Wszystkie "drogi prowadzą do Rzymu": czyli cała inicjalizacja jest skupiona w jednym "głównym" konstruktorze.

Taki sposób nazywa się constructor chaining (łańcuchowanie konstruktorów).

5. Przykłady z życia

Stwórzmy klasę bohater (Hero) do gry, który ma imię i poziom! Tak byśmy to napisali:

Z jednym konstruktorem:

public class Hero
{
    public string Name;
    public int Level;

    public Hero(string name, int level)
    {
        Name = name;
        Level = level;
    }
}

A teraz pozwólmy tworzyć go na różne sposoby!

Wiele konstruktorów:

public class Hero
{
    public string Name;
    public int Level;

    // Jeśli nic nie podano, bohater to "Bezimienny 1 poziomu"
    public Hero() : this("Bezimienny", 1)
    {
    }

    // Jeśli znamy tylko imię, poziom domyślnie — 1
    public Hero(string name) : this(name, 1)
    {
    }

    // Najważniejszy konstruktor z dwoma parametrami
    public Hero(string name, int level)
    {
        Name = name;
        Level = level;
    }
}

Teraz na różne sposoby można stworzyć bohatera:

Hero h1 = new Hero();               // "Bezimienny", 1
Hero h2 = new Hero("Artur");        // "Artur", 1
Hero h3 = new Hero("Lora", 10);     // "Lora", 10

6. Szczegóły implementacji: ukryte pułapki i niuanse

Po pierwsze: kompilator nie tworzy "pustego" konstruktora domyślnie, jeśli sam zadeklarujesz choć jeden inny konstruktor. Więc jeśli napisałeś własny konstruktor z parametrami, ale zapomniałeś dodać bezparametrowy, to taki kod:

Hero h = new Hero(); // Błąd, jeśli nie ma konstruktora bez parametrów!
wywali błąd kompilacji.

Po drugie: jeśli w łańcuchu wywołań konstruktorów wywołasz nie ten, możesz popsuć logikę inicjalizacji. Ważne, żeby trzymać się spójności: najlepiej, żeby cała prawdziwa robota była w najpełniejszym konstruktorze, a reszta tylko przekazywała tam dane przez this(...).

Po trzecie: jeśli parametry różnią się tylko typami (np. Cat(string s) i Cat(object o)), można przypadkiem dostać zamieszanie przy wywołaniu konstruktora z argumentem typu null. Kompilator nie zawsze ogarnie, który konstruktor chcesz wywołać.

7. Przeciążanie i inicjalizacja pól

Często w klasie są pola, które trzeba koniecznie ustawić. Przeciążanie konstruktorów pomaga zrobić to wygodnie, ale to ty decydujesz, jakie wartości domyślne mają sens dla twojej klasy.

Przykład z walidacją:

public class Book
{
    public string Title;
    public int Year;

    // Książka domyślnie — "Bez tytułu", rok 2000
    public Book() : this("Bez tytułu", 2000)
    {
    }
    
    public Book(string title) : this(title, 2000)
    {
    }

    public Book(string title, int year)
    {
        Title = title;
        Year = year;
    }
}

8. Przeciążanie z różnymi zestawami parametrów

Możesz spotkać klasy, gdzie liczba konstruktorów idzie w dziesiątki! Nie zawsze to znak dobrej architektury (czasem lepiej użyć metod konfiguracyjnych albo wzorca "builder"), ale dla obiektów użytkowych, modeli, DTO — całkiem spoko opcja.

Przykład: klasa "Zwierzak", gdzie możesz jawnie podać wszystkie cechy, albo tylko te najważniejsze.

public class Pet
{
    public string Name;
    public int Age;
    public string Type;
    public bool IsVaccinated;

    // Konstruktor domyślny
    public Pet() : this("NoName", 0, "Cat", false) { }

    // Minimum informacji
    public Pet(string name, string type) : this(name, 0, type, false) { }

    // Pełny konstruktor
    public Pet(string name, int age, string type, bool isVaccinated)
    {
        Name = name;
        Age = age;
        Type = type;
        IsVaccinated = isVaccinated;
    }
}
Różnica między przeciążaniem metody a konstruktora
Zwykła metoda Konstruktor (w tym przeciążony)
Można wywołać w dowolnym momencie Wywołuje się tylko przy tworzeniu obiektu (new)
Może zwracać wartość dowolnego typu Nigdy nie zwraca wartości (nie podaje się typu)
Nazwa metody dowolna, zwykle czasownik Nazwa zawsze taka jak klasa
Można przeciążać po parametrach Też przeciąża się po parametrach
1
Ankieta/quiz
Pojęcie klasy i obiektu, poziom 16, lekcja 4
Niedostępny
Pojęcie klasy i obiektu
Klasy i konstruktory
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION