Właściwości

AdamW

Właściwości to specjalna konstrukcja języka C# która symuluje zachowanie pól klasy lub struktury, a w rzeczywistości jest trochę podobna do funkcji.

Przykład deklaracji właściwości:

class Klasa
{
        public int Wlasciwosc
        {
             get
             {
                 // co ma zostać zwrócone przy odwołaniu do tej właściwości
             }
             set
             {
                 // co ma stać się z wartością przypisaną do tej właściwości
             }
        }
}

Taka konstrukcja (czysta właściwość) jest rzadko stosowana, znacznie częściej tworzy się właściwości współpracujące z konkretnym polem klasy.

Przykład:

class Klasa
{
    private int pole; // deklaracja właściwego pola klasy. Jest ono - jak nakazuje dobry zwyczaj programowania obiektowego - ukryte przed obiektami z zewnątrz.
        
    public int Wlasciwosc // właściwość
    {
        get // blok gettera
        {
    	    return pole; // nie robi nic oprócz zwrócenia wartości pola.
        }
        set // blok settera
        {
    	    pole=value; // nie robi nic oprócz przypisania do pola.
        }
    }
}

Od C# 3.0 możliwy jest również zapis skrócony (kod równoważny)

class Klasa
{
    public int Property { get; set; }
}

który oznacza właśnie właściwość zachowującą się jak zwykłe pole. Przez kompilator ten kod jest rozwijany do

[CompilerGenerated]
private int <Property>k_BackingField;

public int Property
{
    get
    {
         return this.<Property>k_BackingField;
    }

    set
    {
        this.<Property>k_BackingField = value;
    }
}

W obydwóch przypadkach jest to maksymalnie okrojona właściwość która w zachowaniu nie różni się niczym od publicznego pola. Jednak bloki get i set wykonują się jak w normalnej metodzie, i nic nie stoi na przeszkodzie żeby je jakoś wzbogacić.

value jest słowem kluczowym mającym specjalne znaczenie jedynie w bloku settera właściwości. Przedstawia on w sposób symboliczny wartość przypisywaną właściwości".

Właściwość która również nie robi niczego pożytecznego, ale demonstruje przykładowe zastosowanie:

class Klasa
{
    private int pole; // deklaracja właściwego pola klasy. Jest ono - jak nakazuje dobry zwyczaj programowania obiektowego - ukryte przed obiektami z zewnątrz.
        
    public int Wlasciwosc // właściwość
    {
        get // blok gettera
        {
            Console.WriteLine("Ktoś pobiera moją wartość!");
    	    return pole; // zwraca wartość pola.
        }
        set // blok settera
        {
            Console.WriteLine("Ktoś zmienia moją wartość!");
    	    pole=value; // przypisuje wartość do pola.
        }
    }
}

Można też tworzyć pola tylko do odczytu nie definiując metody set.

Różnice między właściwościami i polami, rzeczywista reprezentacja właściwości

Na pierwszy rzut oka zmienna:

int Count;

i właściwość (zapis skrócony):

int Count {get; set;}

wyglądają podobnie. Na drugi zresztą też, bo odwoływanie do obydwóch jest takie same, czyli

int i = Count;
Count = i;

Tak naprawdę jednak property (czyli te gettery i settery) mają za zadanie symulować konstrukcje znane np. z C++

private:
    int count;
public:
    int getCount() { return count; }
    void setCount(int value) { count = value; }

Ok, wracamy do C#. I rzeczywiście - na poziomie kodu "maszynowego" (czyli w przypadku C# oczywiście kodu pośredniego) zwykła zmienna zostanie zapisana (w przenośni, nie na poziomie IL)

int Count;

co innego jeśli użyjemy właściwości. zapis int Count {get; set;} da nam w wyniku skądinąd znajomą konstrukcję...

public int get_Count();
public int set_Count(int value);

I w rzeczywistości odwołanie się do zmiennej również jest intuicyjne - czyli zmienna Count = 10; da nam to samo w kodzie wynikowym, gdy tymczasem i = property Count; da w wyniku i = get_Count(); Nie musisz mi wierzyć na słowo, dodaj do dowolnego swojego kodu deklarację funkcji z przedrostkiem get_ tak żeby kolidowała z nazwą właściwości a otrzymasz błąd kompilacji (Type (typ)' already reserves a member called 'get_(nazwa property)' with the same parameter types).

Konsekwencją powyższego (właściwości są metodami) jest też to że można bezproblemowo zakładać na nie breakpointy - nie jest to możliwe w przypadku pól. Tłumaczy to też dlaczego właściwości można umieszczać w interfejsach, a pola już nie.

Jak wiadomo właściwości (czyli jakiekolwiek reagowanie na zmianę / pobranie zmiennej) są jednak czasami potrzebne. W niewielkim projekcie zmienienie zmiennej w właściwość nie powinno być trudne (po prostu wystarczy dopisać {get;set;}) - ale w sporym projekcie może być gorzej, jeśli np. zmienna w jednej klasie zostanie zmieniona we właściwość to wszystkie pozostałe biblioteki korzystające z tej klasy będą musiały być ponownie skompilowane (bo nie znajdą zmiennej nazwanej - zostawmy już to hipotetyczne - Count, a nic ich nie będzie obchodzić metoda get_Count(). ).
Dlatego właśnie dobrym zwyczajem jest też izolowanie pól klasy od świata zewnętrznego.

2 komentarzy

"W obydwóch przypadkach jest to maksymalnie okrojona właściwość która w zachowaniu nie różni się niczym od publicznego pola." <- Czy nie powinno być tutaj napisane "prywatnego pola" zamiast "publicznego pola" ?
Samo pole jest jest tutaj prywatne nie ma do niego bezpośredniego dostępu w innej klasie, jednak mamy do niego dostęp przy pomocy publicznej właściwości.

public int get_Count();
public int set_Count(int value);

nie powinno być:

public int get_Count();
public void set_Count(int value);