1. Automatyczne właściwości
W C# właściwości (property) — to coś pomiędzy polem a metodą. W zasadzie to syntactic sugar, który sprawia, że praca z enkapsulacją jest mega wygodna. Odwołujesz się do właściwości jakby to było zwykłe pole, a w środku — może być dowolna logika: sprawdzanie danych, zmiana innych pól, wywołanie metod, a nawet wysłanie e-maila twojej babci (tego ostatniego nie polecam). W poprzednim wykładzie pisaliśmy właściwości ręcznie. Ale to szybko się nudzi, jeśli masz 100500 prostych obiektów, gdzie potrzebny jest tylko "getter" i "setter" — bez żadnej logiki.
Poznaj — automatyczne właściwości. Pozwolą ci pozbyć się nudnego kodu i sprawią, że C# sam zadba o przechowywanie wartości bez twojego udziału.
Jak było kiedyś: ręczne właściwości
Kiedy chcemy ukryć pole, ale dać do niego dostęp przez właściwość, zwykle piszemy tak:
public class Dog
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
Taki kod to typowa "nuda" (boilerplate). A przecież często nie trzeba nic "dodawać" do tych get/set! Po prostu dać dostęp — i tyle.
Jak można teraz
W C# wszystko stało się prostsze: już nie trzeba ręcznie pisać pól — kompilator robi to za ciebie. Wystarczy napisać:
public class Dog
{
public string Name { get; set; }
}
I gotowe. Możesz spokojnie czytać i zmieniać wartość Name, a pod spodem kompilator sam tworzy prywatne pole, do którego z zewnątrz nie da się dostać bezpośrednio.
Co się tak naprawdę dzieje za kulisami? Gdy w kodzie pojawia się public string Name { get; set; }, kompilator automatycznie tworzy ukryte pole w stylu Name__BackingField. Przy każdym odwołaniu do Name wstawia odpowiedni getter lub setter. Samo pole nie jest widoczne w kodzie, ale istnieje — i przez refleksję można je nawet znaleźć (ale to już inna bajka).
Co jest fajnego w tym podejściu? Po pierwsze, jest dużo krócej i nie trzeba powtarzać tego samego. Po drugie, kod jest czyściejszy: mniej wizualnego szumu, łatwiej czytać i utrzymywać. I wreszcie, to bezpieczniejsze — nikt nie pogrzebie bezpośrednio w polach, dostęp jest tylko przez właściwości.
2. Składnia automatycznych właściwości
Składnia jest bardzo prosta:
public class Dog
{
public string Name { get; set; }
public int Age { get; set; }
}
Teraz nasz Dog wygląda dużo zgrabniej! Możemy tworzyć obiekty i ustawiać wartości po swojemu:
Dog myDog = new Dog();
myDog.Name = "Barbos";
myDog.Age = 4;
Schematycznie
| Sposób | Składnia | Dostęp do wartości |
|---|---|---|
| Pole | |
Dostęp bezpośredni |
| Ręczna właściwość | |
Dostęp przez get/set |
| Auto-właściwość | |
get/set + ukryte pole |
3. Customizacja dostępu: tylko do odczytu, tylko do zapisu
Tylko do odczytu: get-only
Jeśli właściwość ma być tylko do odczytu (np. numer rejestracyjny zwierzaka), można zrobić ją tylko do czytania — podając get, a set w ogóle nie pisać albo zrobić prywatnym. Taką wartość można przypisać tylko w konstruktorze lub od razu przy deklaracji:
public class Dog
{
public string RegistrationCode { get; }
public Dog()
{
RegistrationCode = "DOG-001"; // Przypisujemy wartość w konstruktorze
}
}
Teraz z zewnątrz można tylko odczytać RegistrationCode, ale nie zmienić. Świetny sposób, by chronić ważne dane przed przypadkową zmianą.
Jeszcze opcja: prywatny set
public string Name { get; private set; }
Wtedy właściwość można zmienić tylko w klasie (np. przez metodę lub konstruktor).
Tylko do zapisu: kiepski pomysł
Teoretycznie można zrobić set-only właściwość (tylko do zapisu), ale to rzadko się spotyka i nie jest polecane — wyobraź sobie obiekt, który działa jak "czarna dziura": można coś tam wrzucić, ale już nigdy nie wyciągniesz.
4. init-only settery
Trochę historii
Kiedyś, żeby zrobić niezmienny obiekt (immutable), trzeba było pisać tylko get-właściwości i ustawiać wartości przez konstruktor. OK, wygodne — ale nie zawsze. Ostatnio w C# pojawiła się nowa składnia: init-only właściwości.
O co chodzi z init-setterem?
- Pozwala ustawić wartość właściwości tylko przy tworzeniu obiektu (czyli w konstruktorze albo w inicjalizatorze obiektu).
- Po utworzeniu obiektu właściwość jest tylko do odczytu.
- Świetnie nadaje się do "prawie niezmiennych" klas, DTO, konfiguracji, modeli.
public class Dog
{
public string Name { get; init; }
public int Age { get; init; }
}
Teraz możemy zrobić tak:
Dog dog = new Dog { Name = "Bobik", Age = 2 };
dog.Name = "Rex"; // Błąd! Po utworzeniu - tylko do odczytu
W konstruktorze też można ustawiać wartości:
public Dog(string name, int age)
{
Name = name;
Age = age;
}
Schemat: kiedy można przypisywać
| Gdzie przypisywać? | { get; set; } | { get; private set; } | { get; init; } |
|---|---|---|---|
| Poza klasą, po utworzeniu | ✅ | ❌ | ❌ |
| W konstruktorze | ✅ | ✅ | ✅ |
| W inicjalizatorze | ✅ | ❌ | ✅ |
| Wewnątrz klasy | ✅ | ✅ | ❌ (oprócz konstruktora) |
5. Praktyczne zastosowanie
Przepiszmy naszą klasę Dog, używając automatycznych i init-only właściwości:
public class Dog
{
// Właściwość, ustawiana tylko przy inicjalizacji
public string Name { get; init; }
public int Age { get; init; }
// Automatyczna właściwość dla aktualnego stanu
public bool IsHungry { get; set; }
// Metoda, akcje psa
public void Bark()
{
Console.WriteLine($"{Name} mówi: Hau!");
}
}
Teraz możemy tworzyć Dog tak:
Dog dog = new Dog { Name = "Szaryk", Age = 3, IsHungry = true };
dog.Bark(); // wypisze: Szaryk mówi: Hau!
dog.IsHungry = false; // to właściwość można zmieniać po utworzeniu
dog.Name = "Barbos"; // Błąd! Właściwość tylko do inicjalizacji
6. Automatyczne właściwości z wartościami domyślnymi
W C# wszystko jest dla twojej wygody. Możesz nawet od razu ustawić wartość domyślną właściwości:
public class Dog
{
public string Name { get; set; } = "Bez imienia";
public int Age { get; set; } = 0;
}
Albo z init:
public class Dog
{
public string Name { get; init; } = "Szczeniak";
public int Age { get; init; } = 0;
}
Teraz jeśli nie podasz Name przy tworzeniu, będzie "Szczeniak".
7. Automatyczne właściwości z różnym poziomem dostępu
Możesz zrobić tak, żeby właściwość była czytana i zapisywana z różnymi modyfikatorami dostępu:
public class Dog
{
public string Name { get; private set; }
public int Age { get; private set; }
public Dog(string name, int age)
{
Name = name;
Age = age;
}
}
W tym przypadku zmienić imię i wiek można tylko w klasie Dog, np. w metodach lub konstruktorze.
Porównanie ręcznych i automatycznych właściwości
| Metoda | Trudność pisania | Kontrola w set/get | Zmiana po utworzeniu | Zastosowanie w modelach |
|---|---|---|---|---|
| Ręczna właściwość | Długo | Pełna | Dowolna | Gdy potrzebna logika |
| Automatyczna właściwość | Krótko | Brak | Dowolna lub prywatna | Prawie wszędzie |
| init-only automatyczna | Krótko | Brak | Tylko przy tworzeniu | DTO, konfiguracje |
8. Typowe błędy przy pracy z właściwościami
Błąd nr 1: próba przypisania wartości do get-only właściwości poza konstruktorem.
Jeśli właściwość jest tylko z get, kompilator nie pozwoli zmienić jej wartości gdzie indziej. Przypisywać można tylko w konstruktorze lub od razu przy deklaracji. Zapomnisz — dostaniesz błąd kompilacji.
Błąd nr 2: pomylenie pola z właściwością.
Gdy zamieniasz zwykłe pole na automatyczną właściwość, upewnij się, że w reszcie kodu nie ma bezpośredniego odwołania do pola. Pole i właściwość to różne rzeczy, i jeśli zostawisz stare odwołania, kompilator wywali błąd albo program będzie się dziwnie zachowywał.
Błąd nr 3: prywatny set bez zrozumienia skutków.
Jeśli właściwość ma prywatny set, a w reszcie kodu próbujesz jej przypisać wartość — będzie błąd. Często dzieje się to przypadkiem, zwłaszcza przy kopiowaniu cudzego kodu. Zawsze sprawdzaj, czy set jest dostępny tam, gdzie chcesz go użyć.
GO TO FULL VERSION