WłaściwościCzęść NR.15Hymn właściwości to ważny aspekt. Jak zauważysz pola w klasach .NET są zawsze prywatne natomiast właściwości udostępniają nam zmienne. Chociaż na potrzeby tego kursu na pewno zdarzały się sytuacje, w których pola w klasie czy strukturze były publiczne ,ale nie jest to poprawne zachowanie.

Kiedy chcesz przeczytać bądź zapisać zmienną zazwyczaj używasz symbolu przypisania, czyli znaku równa się ( = ). Dlatego stosowanie metod do wyświetlenia zawartości pól nie jest takim dobry pomysłem. Jest to wykonalne ,ale efekt nie jest taki zadowalający.

Oto przykład. Jak widzisz ilość kodu jest dość duża. Zrobiłem to specjalnie by wykazać ,że to podejście jest również słabe, właśnie z powodu ilości kodu.

struct Punkt3D
{
    public double GetX()
    {
        return this.X;
    }

    public double GetY()
    {
        return this.Y;
    }

    public double GetZ()
    {
        return this.Z;
    }

    public void SetX( double newX)
    {
        this.X = newX;
    }

    public void SetY(double newY)
    {
        this.Y = newY;
    }

    public void SetZ(double newZ)
    {
        this.Z = newZ;
    }

    public Punkt3D(double x,double y,double z)
    {
        X = x; Y = y; Z = z;
    }
    private double X;
    private double Y;
    private double Z;
}

Użyłem w kodzie słowa this. Słowo kluczowe referuje się do obecnej klasy, w której kod jest pisany. W tym wypadku nie jest on konieczny ,ale może ułatwić czytelność kodu.

To jednak jeszcze nic. Jak wygląda zastosowanie takiej struktury w kodzie. Koszmarnie.

Punkt3D p3 = new Punkt3D(43, 43, 43);

double zpos = p3.GetZ();
p3.SetZ(zpos + 30);


Wracając do pól ten sam kod wygląda dużo krócej. Jeśli chcesz zwiększyć jakąś zmienną o jego wartość plus coś, to wystarczy ,że skorzystasz ze skrótu +=.

p3.Z += 30;

Pola nie mogą być publiczne, ponieważ łamie to zasadę hermetyzacji. W dużym skrócie ta zasada mówi ,że program implementujący klasę nie powinien widzieć jak ona działa od środka. Program powinien tylko otrzymywać odpowiednie informacje wyjściowe bez wiedzy jak te dane zostały utworzone.

Mówiąc jeszcze prościej nie wszystkie dane powinny być dostępne ,aby ich nie można było zmienić w trakcie działania klasy. Najprostszym przykładem jest np. struktura CzasPracy . Powiedzmy ,że posiada ona pola : sekundy, minuty i są one deklarowane w konstruktorze klasy ,a w nim wykonuje się dzielenie modulo przez liczbę 60. W ten sposób sekundy i minuty nie będą miały wartości powyżej 60.

struct CzasPracy
{
    public CzasPracy(int s,int m)
    {
        sekundy = s % 60;
        minuty = m % 60;
    }

    public int sekundy;
    public int minuty;
}

Ale jeśli pola są publiczne cały mój wysiłek poszedł na marne. Ponieważ w każdej chwili programista może podać błędne wartości. Sprawiając ,że cel tej klasy jest niespełniony.

Problem może nie jest taki wielki wystarczy , że pola będą prywatne, ale co, jeśli chcę mieć dostęp do tych pól, aby je jakoś wyświetlić. Już wiemy , że metody odpadają.

W takim wypadku do gry wchodzą właściwości.

Czym są właściwości

Właściwości to mechanizm łączący zachowania pól i metod i wypełniają tę właśnie lukę w C#.

Właściwości wyglądają jak pola więc możemy wyświetlić ich wartość bądź je przypisać ,a zarazem zachowują się jak metody, czyli możemy stosować je w interfejsach.

Możesz sobie nie zdawać z tego sprawy , ale już nieraz używałeś właściwości w klasach .NET jak np. Console. W Intellisense Visual Studio właściwości mają ikonkę listy z rączką. Możesz z opisu każdej właściwości wyczytać czy są one tylko do odczytu (GET) , czy też można je ustawić (SET). Pola tego nie potrafią.

własciwości

Pola mają ikonkę niebieskiego sześcianu. (bądź umieść tutaj swoją interpretacje, szczerze mówiąc nigdy się nie zastanawiałem jak dokładnie te ikonki wyglądają i co reprezentują) xD

Pola

Jak mówiłem wcześniej wszystkie klasy w .NET mają pola prywatne czyli raczej tej ikonki nie zobaczysz wewnątrz jakieś struktury, czy klasy.

Zobaczmy jak deklaruje się właściwości.

ModyfikatorDostępu [Typ] NazwaWłaściwości
{
    get 
    {
        //Kod, który odczytuje właściwość         
    }

    set 
    {
        //Kod, który ją zwraca         
    }
}

Właściwości zawierają dwa bloki kodu zaczynające się od słów kluczowych GET i SET.

Blok kodu GET zawiera stwierdzenia, które wykonują się, gdy właściwość jest odczytywana . Blok SET zawiera kod, który się wykona, gdy właściwość zostanie zapisana.

Zmieńmy kod struktury z Punktu3D tak, aby korzystała ona z właściwości.

Visual Studio ułatwia nam życie po raz kolejny . Korzystając z opcji “Encapsulate Field ” Visual Studio może wygenerować dla nas właściwość dla danego pola prywatnego trzeba go tylko zaznaczyć.

Refactor

Oto kod Punktu3D z właściwościami.

struct Punkt3D 
{
    private double x, y, z;

    public double Z
    {
        get { return z; }
        set { z = value; }
    }

    public double Y
    {
        get { return y; }
        set { y = value; }
    }

    public double X
    {
        get { return x; }
        set { x = value; }
    }

    public Punkt3D(double X,double Y,double Z)
    {
        x = X; y = Y; z = Z;
    }
}

Zwróć uwagę na to ,że właściwości zaczynają się wielką literą ,a pola z małej. Jest to ogólnie przyjęta konwencja nazw.

  • prywatne pola powinny być pisane małą literą
  • właściwości z dużej

Jak widzisz w bloku kodzie SET zostało użyte słowo kluczowe value.Słowo referuje się do wartości przekazanej przez nas do właściwości. Ilość kodu jest dużo mniejsza niż w przypadku zastosowania metod bezpośrednio.

Dla bloku kodu GET widzisz znane ci słowo kluczowe return. Metoda ta ma zwracać parametr i za pomocą tego słowa kluczowego właśnie to robi.

Błąd nazewnictwa i przepełnienie stosu
Trzeba pamiętać o wielkości znaków przy pisaniu właściwości. Skoro pole prywatne i właściwość różni się tylko wielkością litery ten błąd może się zdarzyć.

image

W takim wypadku program, gdy spróbuje uzyskać dostęp do właściwości “Sekundy” po kilku sekundach wyrzuci wyjątek StactOverflow. Program się zapętli próbując uzyskać dostęp do właściwości, która jest powiązana ze sobą . Pętla się nie przerwie, ale przepełni stos. Visual Studio nie pilnuje tego więc błąd taki łatwo popełnić.


Przykład ten w pełni nie przedstawia wszystkich zalet stosowania właściwości. Wróćmy do struktury CzasPracy. Korzystając z właściwości możemy mieć pełną kontrolę nad wartościami jakie podaje użytkownik.


struct CzasPracy
{
    private int sekundy;

    public int Sekundy
    {
        get { return sekundy; }
        set { sekundy = CheckTime(value); }
    }

    private int minuty;

    public int Minuty
    {
        get { return minuty; }
        set { minuty = CheckTime(value); }
    }
    //Statyczna prywatna metoda sprawdzająca poprawność danych 
    private static int CheckTime(int czas)
    {
        return czas % 60;
    }

    public CzasPracy(int s,int m)
    {
        sekundy = CheckTime(s);
        minuty = CheckTime(m);
    }
}

W strukturze stworzyłem metodę statyczną CheckTime, która sprawdza poprawność wpisanych zmiennych tak, aby nie powielać tego samego kodu. Metoda ta może być użyta w bloku kodu SET . Od tej pory użytkownik nawet po utworzeniu struktury nie może nadać zmiennym “sekundy” i “minuty” wartości powyżej 60. Chociaż wciąż wartości mogą być ujemne Puszczam oczko To tylko przykład.

Po dodaniu właściwości struktury i klasy zaczynają wyglądać po ludzku

Po dodaniu właściwości struktury i klasy zaczynają wyglądać bardziej po ludzku. W następnym wpisie do struktur powiem jak przeciążać operatory +,-,

Jak działają właściwości

Kiedy używasz właściwości zobaczysz ,że ich działanie jest zbliżone do pól. Gdy pobierasz właściwość wykonuje się blok kodu GET ,gdy zmieniasz wartość właściwości poprzez przypisanie wykonuje się blok kodu SET.

W pewnym sensie są to metody ,ale właściwość automatycznie tłumaczy taki fragment kodu więc właściwości wyglądają jak pola dlatego taki fragment kod jest całkowicie poprawny.

CzasPracy czas = new CzasPracy(10, 0);

int m = czas.Minuty; //Blok GET dla Minuty 
int s = czas.Sekundy; //Blok GET dla Sekundy 

czas.Minuty = m; //Blok SET dla Minuty 
czas.Sekundy = s; //Blok SET dla Sekundy 
czas.Sekundy -= 10; //Blok GET i SET dla Sekundy 
czas.Minuty += 10;  //Blok GET i SET dla Sekundy

Właściwości mogą też wykonywać zadania, które wymagają równocześnie odczytu i zapisu. Jak widać w dwóch ostatnich fragmentach kodu.

Tylko do odczytu właściwości

Skoro mamy do dyspozycji dwa bloki kodu GET i SET to, co się stanie, jeśli jeden z nich nie zostanie zapisany.

Jest to możliwe i kompilator nie uzna tego za błąd.

Właściwość bez słowa kluczowego SET będzie tylko do odczytu.

public int Sekundy
{
    get { return sekundy; }
}

W wypadku próby zapisu właściwości kompilator zgłosi błąd.

Tylko do odczytu

Tylko do zapisu właściwości

W podobny sposób możesz stworzyć właściwość tylko do zapisu.

public int Minuty
{
    set { minuty = CheckTime(value); }
}

Tak samo, jak wcześniej próba, tym razem odczytu zakończy się błędem kompilatora.

Tylko do odczytu

Właściwość taka może wydawać się dziwolągiem, fakt dużo takich właściwości nie spotkasz. Właściwości takie są używane np. do przechowywania haseł. Hasła można zmienić ,ale nie powinno dać się ich przeczytać.

Jednak uwaga. Co powinieneś wiedzieć teraz:

  • Właściwości muszą posiadać chociaż jedno słowo kluczowe GET lub SET.
  • GET i SET nie mogą pobierać żadnych parametrów.
  • Właściwość składa się tylko z dwóch bloków kodów GET i SET żadnych dodatkowych pól,metod i kolejnych właściwości.

Dostępność właściwości

Usuwanie słów kluczowych dostępu GET SET usuwa też tą funkcjonalność wokół klasy.
Oczywiście istnieją sytuacje, w których właściwość musi mieć tą funkcjonalność ,ale tylko w odpowiednich sytuacjach.
Dlatego właściwości i ich dostępność do odczytu i zapisu może być określona modyfikatorem dostępu.

public int Sekundy
{
    get { return sekundy; }
    private set { sekundy = CheckTime(value); }
}

Normalnie dostępność tych dwóch bloków kodu jest powielana od ogólnego modyfikatora dostępu dla właściwości, w tym przykładu “public int Sekundy” ,ale może być ona nadpisana.

Powinieneś się domyślić , że w takim wypadku nie może zajść sytuacja gdy:

  • Oba modyfikatory dostępu dla GET i SET są nadpisane. Z logicznego punktu widzenia powinieneś zmienić dostępność ogólną właściwości , gdy taka sytuacja nastąpi. W takim wypadku kompilator zgłosi błąd.Błąd nadpisanie dwóch accessorów
  • Modyfikatory dostępu dla GET i SET nie mogą być bardziej otwarte niż jest ogólnie dostępna właściwość. Czyli jeśli właściwość jest prywatna bądź chroniona to dostępność GET lub SET nie może być publiczna. Gdy chcesz zablokować dostępność dla GET lub SET uczyń właściwość publiczną ,a potem zmień dostępność jednego z nich na prywatną.Błąd większa otwartość niż właściwość posiada

Ograniczenia i Zasady ich użycia, czyli właściwości to nie pola

Właściwości wyglądają jak pola . Słowo “wyglądają” dobrze wyraża pewną dużą różnicę pomiędzy polami  a właściwościami.

Właściwość może być przypisana tylko, gdy struktura i klasa jest zainicjowana. Czyli cała rozmowa o możliwość użycia struktur bez słowa kluczowego new w poprzednim odcinku kursu teraz na pewno może pójść do kosza.Puszczam oczko. Skoro pola muszą być prywatne ,a właściwości nie mogą być użyte bez inicjacji.

Poniższy kod dla struktury i właściwości wywoła błąd.

Struktura bez new

Właściwości to nie pola więc nie mogą być używane ze słowami kluczowymi REF i OUT. Czyli jeśli metoda potrzebuje zmiennej z REF i OUT właściwość bezpośrednio nie może być użyta. Ma to całkowity sens logiczny w końcu właściwości nie odnoszą się do specyficznego miejsca w pamięci.

TryParse

Właściwość nie mogą być stałe zresztą po co, miałyby być. Jak popatrzysz na stałe pola dla int i float zobaczysz inną ikonkę niż mają właściwości.

Stała

Są to wciąż pola. Próba deklaracji właściwości ze słowem const skończy się błędem.

const

Interfejsy i właściwości

Powiedziałem wcześniej ,że w interfejsach nie można deklarować pól, tylko metody. Ponieważ w pewnym sensie właściwości zachowują się jak metody możemy je zadeklarować w interfejsach.

Właściwości w interfejsach nie mogą mieć żadnego ciała więc słowa kluczowe GET i SET kończą się średnikiem.

interface ICzas
{
    int Minuty { get; set; }
    int Sekundy { get; set; }
}

Każda klasa albo struktura musi implementować te właściwości z dwoma funkcjami GET i SET.

Wirtualne właściwości

W klasie możesz tworzyć wirtualne właściwości. W ten sposób mogą one być nadpisane przez kolejne klasy pochodne.

class Ssak 
{
    private int liczbanog;

    public virtual int Liczbanog
    {
        get { return liczbanog; }
    }

}

class Kot : Ssak
{
    public override int Liczbanog
    {
        get { return 4; }
    }
}

Jak widzisz to działa i właściwość może być nadpisana.

Występują też właściwości abstrakcyjnie,ale tylko dla klas abstrakcyjnych.

Inicjowanie obiektów przy użyciu właściwości

Podobnie jak z tablicami możesz nadać właściwościom gotowe wartości w czasie inicjacji używając nawiasów klamrowych.

właściwości

Jest to pewna alternatywa dla konstruktorów. Konstruktor mogą nie wypełniać wszystkich interesujących nas pól. Kiedy napiszesz taki kod kompilator uruchomi najpierw konstruktor ,a zaraz potem blok kodu SET dla każdej uzupełnionej w ten sposób właściwości.


CzasPracy czas = new CzasPracy() { Minuty=10,Sekundy=12 };

Czasami jest to przydatne.

Zalety właściwości

  • Kompatybilność z Interfejsami
  • Lepsze zarządzanie aplikacją

Co dalej:

W następnym wpisie dodamy operator +,- do struktury i klasy.