Konstruktory w C# – przeciążanie, this(), i primary constructors
Konstruktor to metoda, która gwarantuje, że obiekt nigdy nie istnieje w niepełnym, niepoprawnym stanie — zanim ktokolwiek zdąży użyć instancji Zamowienie, konstruktor już wymusił, że ma ona KlientId i przynajmniej jedną pozycję. To nie szczegół składniowy — to fundamentalna gwarancja, na której opiera się bezpieczeństwo typów w C#.
Podstawowa składnia
public class Zamowienie
{
public int KlientId { get; }
public DateTime DataUtworzenia { get; }
public Zamowienie(int klientId)
{
KlientId = klientId;
DataUtworzenia = DateTime.UtcNow; // konstruktor może zawierać dowolną logikę inicjalizującą
}
}
Konstruktor ma tę samą nazwę co klasa, nie ma typu zwracanego (nawet nie void) i wykonuje się dokładnie raz — w momencie new Zamowienie(5).
Przeciążanie konstruktorów
public class Zamowienie
{
public int KlientId { get; }
public string Uwagi { get; }
public Zamowienie(int klientId) : this(klientId, "") { } // deleguje do drugiego konstruktora
public Zamowienie(int klientId, string uwagi)
{
KlientId = klientId;
Uwagi = uwagi;
}
}
var a = new Zamowienie(5); // Uwagi = ""
var b = new Zamowienie(5, "Pilne!"); // Uwagi = "Pilne!"
: this(klientId, "") to łańcuchowanie konstruktorów — zamiast powtarzać logikę inicjalizacji w każdym przeciążeniu, jeden konstruktor woła drugi z wartością domyślną. To eliminuje duplikację: jeśli logika inicjalizacji się zmieni, poprawiasz ją w jednym miejscu, nie we wszystkich przeciążeniach.
Konstruktor domyślny — znika, gdy zdefiniujesz jakikolwiek inny
public class PunktZly
{
public int X, Y;
public PunktZly(int x, int y) { X = x; Y = y; }
}
var p = new PunktZly(); // ❌ błąd kompilacji — nie ma konstruktora bezparametrowego!
Jeśli klasa nie ma żadnego zdefiniowanego konstruktora, kompilator automatycznie dodaje pusty, bezparametrowy — dlatego new PunktZly() zawsze działało w prostych klasach. Ale w momencie, gdy zdefiniujesz jakikolwiek własny konstruktor, automatyczny znika całkowicie — jeśli wciąż chcesz mieć możliwość new PunktZly(), musisz dopisać go jawnie, obok pozostałych.
Primary constructors (C# 12) — konstruktor wbudowany w deklarację klasy
public class Zamowienie(int klientId, string uwagi = "")
{
public int KlientId { get; } = klientId;
public string Uwagi { get; } = uwagi;
public bool JestPilne() => uwagi.Contains("Pilne"); // parametr dostępny w całej klasie
}
Parametry w nawiasach zaraz po nazwie klasy ((int klientId, string uwagi = "")) tworzą primary constructor — nie trzeba już pisać osobnego bloku public Zamowienie(...) { ... }. Parametry są dostępne w całym ciele klasy: w inicjalizatorach właściwości i wewnątrz metod (jak uwagi w JestPilne()).
Pułapka: primary constructor w class NIE tworzy automatycznie publicznych właściwości
public class ZlyPrzyklad(string imie)
{
// brak jawnej właściwości!
}
var osoba = new ZlyPrzyklad("Anna");
Console.WriteLine(osoba.imie); // ❌ błąd kompilacji — 'imie' nie jest publiczną właściwością
To najczęstsze mylne wyobrażenie o primary constructors w zwykłych klasach: parametr imie jest dostępny wewnątrz klasy, ale nie staje się automatycznie publiczną właściwością na zewnątrz. Żeby go wystawić, musisz jawnie zadeklarować właściwość:
public class DobryPrzyklad(string imie)
{
public string Imie { get; } = imie; // ✅ jawna właściwość, przypisana z parametru
}
To zachowuje się inaczej niż w rekordach — pozycyjny record automatycznie tworzy publiczne właściwości init z parametrów konstruktora:
public record Osoba(string Imie); // Imie JEST publiczną właściwością — automatycznie
var osoba = new Osoba("Anna");
Console.WriteLine(osoba.Imie); // ✅ działa — bo to record, nie zwykła class
Jeśli mylisz te dwa zachowania, dowiesz się o tym dopiero na błędzie kompilacji — warto zapamiętać regułę: primary constructor w class to tylko wygodniejsza składnia konstruktora; w record to od razu pełne, publiczne właściwości. Więcej o różnicach między nimi w class vs struct vs record.
Konstruktor kontra inicjalizator obiektowy
// Konstruktor — wymusza podanie wartości, kolejność argumentów ma znaczenie
var a = new Zamowienie(klientId: 5, uwagi: "Pilne");
// Inicjalizator obiektowy — czytelniejszy przy wielu polach, ale nic nie wymusza
var b = new Zamowienie { KlientId = 5, Uwagi = "Pilne" }; // wymaga publicznego settera albo 'init'
Inicjalizator obiektowy ({ Pole = wartosc }) jest czytelniejszy, gdy ustawiasz wiele opcjonalnych pól — nazwy pól przy każdej wartości eliminują pomyłkę kolejności argumentów. Cena: bez required (patrz niżej) nic nie wymusza, żeby ktokolwiek w ogóle ustawił kluczowe pola — łatwo przez przeoczenie zostawić obiekt z domyślnymi, niepoprawnymi wartościami.
Konstruktor statyczny — inicjalizacja raz, dla całej klasy
public class Konfiguracja
{
public static readonly string SciezkaLogow;
static Konfiguracja() // brak modyfikatora dostępu, brak parametrów
{
SciezkaLogow = Environment.GetEnvironmentVariable("LOG_PATH") ?? "/var/log/app";
Console.WriteLine("Konstruktor statyczny wykonany raz");
}
}
Konstruktor statyczny wykonuje się dokładnie raz na całą aplikację, automatycznie, tuż przed pierwszym użyciem klasy (pierwsze odwołanie do statycznego pola/metody albo pierwsze new) — nie wywołuje się go ręcznie. Służy do inicjalizacji statycznych pól, które wymagają logiki bardziej złożonej niż proste przypisanie literału.
required — alternatywa dla primary constructors przy walidacji
public class Zamowienie
{
public required int KlientId { get; init; } // MUSI zostać ustawione przy tworzeniu
}
var z = new Zamowienie(); // ❌ błąd kompilacji — KlientId nie zostało ustawione
var z2 = new Zamowienie { KlientId = 5 }; // ✅
required (C# 11) wymusza ustawienie właściwości przy tworzeniu obiektu przez inicjalizator obiektowy ({ KlientId = 5 }), bez pisania osobnego konstruktora. To dobra alternatywa, gdy wolisz składnię inicjalizatora od wywołania konstruktora z pozycyjnymi argumentami — szczególnie przy wielu opcjonalnych polach, gdzie kolejność argumentów konstruktora byłaby trudna do zapamiętania.
Podsumowanie
Konstruktor gwarantuje, że obiekt nigdy nie istnieje w niepoprawnym stanie. Przeciążanie plus this(...) eliminuje duplikację logiki inicjalizującej między wariantami. Primary constructors (C# 12) skracają najprostszy przypadek — ale pamiętaj o kluczowej różnicy: w zwykłej class parametr jest dostępny tylko wewnątrz, nie staje się publiczną właściwością automatycznie; w record staje się. Konstruktor statyczny inicjalizuje dane wspólne dla całej klasy, dokładnie raz, automatycznie — a required bywa czytelniejszą alternatywą dla konstruktora, gdy zależy ci na składni inicjalizatora obiektowego.
🚀 Co dalej?
Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
Zamień wiedzę w umiejętności
Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.
Pobieram roadmapę →
1 comment