🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

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.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 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.

czytanie to początek

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ę →