1. Wprowadzenie
Zacznijmy od problemów, które pojawiają się przy bezpośrednim użyciu pól. Jeśli zadeklarujesz pole jako public, można je zmieniać od razu i bezpośrednio z dowolnego miejsca w programie:
public class Dog
{
public string Name;
}
Dog dog = new Dog();
dog.Name = ""; // O_o ... to jak "imię psa = pusty string"!
Użytkownik może przypisać polu Name zupełnie absurdalne wartości, nawet takie, które nie mają sensu w twojej domenie: pusty string, za długie imię, albo nawet null. To jakby ktoś składał twoją nową szafę z IKEI, a ty dajesz mu pełny dostęp do fabryki stolarskiej.
Przypominam, że enkapsulacja to wtedy, gdy obiekt sam kontroluje swoje dane. Nie ufamy każdemu przypadkowemu typowi, tylko dajemy specjalne "drzwiczki" — właściwości (Properties), przez które odbywa się dostęp do pola, ale z możliwością sprawdzenia, logowania, modyfikacji albo innych reakcji na zmianę wartości.
2. Definicja i składnia
Właściwość to specjalny członek klasy, który wygląda prawie jak pole, ale pod spodem to para specjalnych metod: getter (pobierz wartość) i setter (ustaw wartość). Dzięki właściwości możemy:
- Pozwolić (albo zabronić) odczyt/zapis danych;
- Dodać walidację lub logikę przy dostępie do danych;
- Ukryć wewnętrzne pole, a nawet trzymać wartość gdzieś indziej.
Właściwość deklaruje się bardzo podobnie do pola, tylko z klamrami i słowami kluczowymi get i set w środku.
[modyfikator_dostępu] typ NazwaWłaściwości
{
get { ... }
set { ... }
}
Oto przykład dla naszej klasy Dog:
public class Dog
{
private string _name; // pole wewnętrzne (private!)
public string Name
{
get { return _name; } // "getter": pobierz imię
set { _name = value; } // "setter": ustaw imię
}
}
Uwaga: podkreślenie zwykle używa się dla prywatnych pól (_name). To standard stylu C#.
3. Jak działa właściwość
Właściwość to taki „strażnik”, który stoi między wewnętrznymi danymi obiektu a światem zewnętrznym. Przykład:
Dog dog = new Dog();
dog.Name = "Szarik";
Console.WriteLine(dog.Name);
Wyjaśnienie:
- Kiedy program dochodzi do linii dog.Name = "Szarik"; — wtedy wywołuje się metoda set właściwości i możesz tam dodać dowolną potrzebną walidację (np. sprawdzić, czy imię nie jest puste).
- W momencie Console.WriteLine(dog.Name); wywołuje się metoda get, która po prostu zwraca aktualną wartość albo, jeśli trzeba, coś dynamicznie wyliczonego.
Wygląda, jakby to było zwykłe pole, ale tak naprawdę wszystko jest pod kontrolą!
4. Czemu właściwości to "Best Practice"
W większości przypadków nie dajemy bezpośredniego dostępu do wewnętrznych pól obiektu. Nawet jeśli teraz wydaje się, że walidacja nie jest potrzebna, nawyk opakowywania danych we właściwości mega się przydaje, gdy zasady gry się zmienią.
Przykład "walidacji" przy przypisaniu:
public class Dog
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("Imię psa nie może być puste!");
}
_name = value;
}
}
}
Teraz coś takiego:
dog.Name = ""; // Rzuci wyjątek!
… chroni nasz obiekt przed absurdalnymi wartościami.
5. Właściwości: tylko do odczytu, tylko do zapisu i zwykłe
Czasem trzeba pozwolić tylko na podgląd wartości (np. pies ma tylko rok urodzenia, nie można go zmienić), albo odwrotnie — tylko ustawianie (rzadko, ale może chcesz zrobić coś tajemniczego).
- Tylko do odczytu: piszemy tylko get, usuwamy set.
- Tylko do zapisu: piszemy tylko set, usuwamy get.
Przykłady:
public class Dog
{
private int _birthYear = 2018;
// Tylko do odczytu
public int BirthYear
{
get { return _birthYear; }
}
// Tylko do zapisu (bardzo rzadko spotykane)
public string Secret
{
set { /* robimy coś z value */ }
}
}
6. Właściwości vs pola
| Pole | Właściwość | |
|---|---|---|
| Składnia | |
|
| Dostęp | Bezpośredni | Przez get/set |
| Walidacja | Brak | Można dodać w set/get |
| Rozszerzalność | Brak | Można modyfikować w każdej chwili |
| Integracja z IDE | Widoczne jako pola | Widoczne jako właściwości (ważne dla frameworków) |
Ilustracja: Jak działa właściwość
sequenceDiagram
participant User as Użytkownik obiektu
participant Dog as Obiekt Dog
participant Field as Prywatne pole _name
User->>Dog: dog.Name = "Ryżyk"
Dog->>Dog: set Name("Ryżyk")
Dog->>Field: _name = "Ryżyk"
User->>Dog: print(dog.Name)
Dog->>Dog: get Name()
Dog->>Field: czyta _name
Dog->>User: zwraca "Ryżyk"
7. Typowe błędy i niuanse
Zobaczmy, gdzie mogą czaić się pułapki.
Częsty błąd — pomylić pola i właściwości, przypadkowo wystawić prywatne dane na zewnątrz:
public string name; // To pole! Wszędzie widoczne, niebezpieczne!
Lepiej tak:
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
Statyczne właściwości istnieją, żeby trzymać wartości wspólne dla wszystkich obiektów. Ale używaj ich ostrożnie.
Ciekawostka: jeśli właściwość ma tylko get i żadnego konstruktora, to nie da się jej w ogóle zmienić — to się nazywa immutable property i jest mocno używane w nowoczesnych podejściach do projektowania (wrócimy do tego szerzej w lekcjach o record i niezmienności danych).
GO TO FULL VERSION