Gettery i settery w C# - kilka pytań

0

Witam, mam kilka pytanek, jednak najpierw kod:

    class Liczby
    {
        public int a;
        public int liczba {
            get 
            {
                Console.WriteLine("Pobrano wartość");
                return a;
            }
            set
            {
                Console.WriteLine("Zmieniono wartość");
                a = value;
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Liczby cyfra = new Liczby();
            cyfra.liczba = 5;
            int x = cyfra.liczba;
            Console.WriteLine(x.ToString());
            Console.Read();
        }
    }

1.Czy dobrze rozumuję zastosowanie "getterów" i "setterów" ? Patrz: WriteLine'y.
2.Czy to:

int a { get; set; }

jest równoznaczne z tym?

int a;

Jeśli nie: to po co takie "puste" get i set?
3.Jakie jest praktyczne zastosowanie get i set w aplikacjach/ grach/ hackach itp.? Mile widziane przykłady.
A i proszę o niezbyt skomplikowane terminy :D.

2

Twój kod nie bardzo ma sens, bo do zmiennej a i tak możesz dostać się z zewnątrz bo jest public. Powinna być private.

  1. No dobrze, ciężko źle zrozumieć bo jak sama nazwa wskazuje get pobiera wartość, a set ustawia.
  2. Nie. W pierwszym przypadku kompilator utworzy prywatną zmienną, do której będziesz mógł się dostać tylko przy pomocy akcesorów. W drugim masz zwykłą zmienną.
  3. Praktyczne zastosowanie to np. hermetyzacja.
0

A, i skoro pierwszy zapis tworzy prywatną zmienną, to po co pisać tak:

int a { get; set; }

Skoro można tak?

private int a;
0

A potem będziesz pisał gettery/settery do każdej zmiennej ?

1

Pierwsze jest właściwością (property) a drugie polem (field).

W działaniu są takie same, ale właściwość zawsze będziesz mógł rozbudować o gettera i settera, a pole musiałbyś zamienić na właściwość albo funkcje. W przypadku np. publicznego API taka zmiana byłaby problematyczna.

Zaleca się nie robić publicznych pól, tylko właściwości.

0

Pomyślałem, że można to wykorzystać do zabezpieczenia programu w taki sposób, że możliwa jest tylko inkrementacja zmiennej (gracze wiedzą, że jednorazowo awansuje się tylko o jeden poziom wyżej) i stworzyłem taki kod:

    class liczby
    {
        private int a = 0;
        public int liczba {
            get
            {
                Console.WriteLine("Pobrano wartość");
                return a;
            }
            set
            {
                if (value != a+1)
                    Console.WriteLine("Nieprawidłowa wartość!");
                else
                {
                    Console.WriteLine("Poprawna inkrementacja - zmieniono wartość");
                    a++;
                }
            }  
        }   
    }
    class Program
    {
        static void Main(string[] args)
        {
            int a=0;
            liczby cyfra = new liczby();
            cyfra.liczba = 1;
            Console.WriteLine(cyfra.liczba.ToString());
            while (a!= -1)
            {
                a = int.Parse(Console.ReadLine());
                cyfra.liczba += a;
                Console.WriteLine(cyfra.liczba.ToString());
            }
        }
    }

Czy był to dobry pomysł? Można inaczej-lepiej ? Czy mój kod nie jest zły?

2

Czy był to dobry pomysł? Można inaczej-lepiej ? Czy mój kod nie jest zły?

Lepszym byłaby raczej funkcja inkrementująca wartość.

public int LevelUp()
{
  return ++a;
}
1

@Azarien
Ostatnio oglądałem materiał-ciekawostkę opisujący zalety tworzenia większości klas jako niemutowalnych i tłumaczący odradzał m.in. trzymanie logiki w setterach, z racji potrzebnej większej ilości testów i potencjalnych efektów ubocznych.
Przyznam, że mnie temat zaciekawił, bo wygląda na to, że takie zamrożenie stanu obiektu, faktycznie ułatwiło by testowanie tylko ciekawi mnie jakie haczyki w tym ew. się kryją.
Ten przykład pewnie napisałby w stylu:

class liczby
    {
        private readonly int a;
        public liczby(int a)
        {
            this.a = a;
        }
        public liczby LevelUp()
        {
            return new liczby(++a);
        }

    }
 
3
Min0s napisał(a):

Pomyślałem, że można to wykorzystać do zabezpieczenia programu w taki sposób, że możliwa jest tylko inkrementacja zmiennej (gracze wiedzą, że jednorazowo awansuje się tylko o jeden poziom wyżej) i stworzyłem taki kod

To nie służy do zabezpieczania programów, co najwyżej do pisania hermetycznego kodu.

Czy był to dobry pomysł? Można inaczej-lepiej ? Czy mój kod nie jest zły?

Jest zły, bo we właściwościach piszesz coś na konsoli. Obiekt, który przechowuje jakieś dane nie powinien się zajmować operacjami wejścia/wyjścia, to łamanie zasady jednej odpowiedzialności.

QwertzOne napisał(a):

Ostatnio oglądałem materiał-ciekawostkę opisujący zalety tworzenia większości klas jako niemutowalnych i tłumaczący odradzał m.in. trzymanie logiki w setterach, z racji potrzebnej większej ilości testów i potencjalnych efektów ubocznych.

Zależy jakiej logiki. Jeśli to ma być naliczanie rabatu na podstawie historii zakupów klienta, to faktycznie słaby pomysł. Ale jeśli inkrementacja jakiejś wartości, walidacja albo rzucenie zdarzenia zmiany wartości, to już ma sens.

Przyznam, że mnie temat zaciekawił, bo wygląda na to, że takie zamrożenie stanu obiektu, faktycznie ułatwiło by testowanie tylko ciekawi mnie jakie haczyki w tym ew. się kryją.

Jedyny haczyk to chyba to, że modyfikacja obiektu powoduje tak naprawdę utworzenie nowego, więc tych obiektów robi się dużo i GC ma więcej pracy.

1

Dobrą praktyką jest hermetyzacja danych. Dane są prywatne lub chronione, kiedy mają być wykorzystywane jedynie wewnątrz tej samej klasy/ klasy tej oraz pochodnej. Czasami bywa, że są dane które chcemy z klasy pobrać, oczywiście muszą być publiczne. Możemy wykorzystać zwykłe zmienne lub właściwości.

Pierwsza rzeczą są przyzwyczajenia nasze oraz ogólna koncepcja Microsoftu. Mając dokumentacje np. klasy widzisz jakie posiada metody oraz właściwości. Nie masz za to pól. VS inaczej oznacza property, wiec też z przyzwyczajenia szukamy tego symbolu klucza jako property.

Wyobraź sobie teraz że masz wielki projekt. Robimy jakąś aplikacje w której użytkownik dostanie wartość w jednostkach metr. Jednak pod koniec projektu chcemy zmienić na centymetry. Zmiana wartości pokrzyżuje nam wewnętrzne przeliczenia w klasie. Property używamy tylko do wyświetlania wyniku. Wiec nic nie szkodzi jak w property damy getera który pomnoży naszą wartość dodatkowo o 100.

Property można traktować jako funkcje/metody które pobierają nam potrzebną wartość z klasy. Skoro pobieramy ją z zewnątrz to jest publiczna. Ale możemy narzucić jej ograniczenia, np uniemożliwić jej zmianę wartości z zewnątrz (prywatny setter). W innych językach pisało się metody get_Nazwa() do pobrania danej i set_Nazwa(wartosc) do zmiany, a tutaj to zapewniają nam property

Podsumowując zalety to:

  1. Elastyczność - możemy odwoływac się w dwojaki sposób do zmiennej
  2. Enkapsulacja- property pokazuja nam co możemy pobrac, nie naruszamy klasy wewnętrznej- najważniejsza cecha
  3. Mniejszy kod- zwykła modyfikacja zmiennej o 100 może powodować wiele wiecej linijek kodu czasami,a geter załatwi nam to w jednej
  4. praktyczność- według mnie lepsze niż definiowanie metod do tego, zarazem jest to bardziej czytelne
1

Na świetny przykład jak nie używać getterów i setterów natknąłem się ostatnio tutaj:
http://mcfunley.com/from-the-annals-of-dubious-achievement
Cały tekst jest wart przeczytania, ale o akcesorach jest ostatni paragraf:

I guess the StackOverflow snippet doesn't capture this, but the best thing about Richard's code was that he loved property getters and setters. No, wait, that's not quite right. Lots of people love getters and setters, but Richard seemed to be in love with getters and setters. So much so that about 70% of his logic took place in them. More than once I deleted code that looked like this:
foo.x = foo.x;
Only to break entire pages, because the side effects of that assignment were doing everything. Anyway, I hope you can all see where I was coming from now.

5

imo pakowanie jakiejkolwiek logiki do get/set to prawie zawsze zly pomysl, tak samo jak posiadanie nieprywatnego settera w klasach innych niz dto.
jesli potrzebujemy miec jakas logike w klasie (a juz zwlaszcza zmieniajaca jej stan) to najlepiej zastanowic sie nad stworzeniem metody, ktorej nazwa w sensowny sposob powie czego tak naprawde sie mozemy po niej spodziewac.

0

Zatem co najlepiej umieszczać w get/set?
Lepiej tak:

int sprawdz (int co) {
int wynik=0;
if (co>5)
	wynik=10;
return wynik;
}
int liczba=0;
int Liczba {
	set {
		sprawdz(value);
	}
}

niż

int liczba=0;
int Liczba 
{
	set {
	if (value>5)
		liczba=10;
return liczba;
	}
}

?
//Najpierw dostaję ochrzan za pisanie postów, a teraz za komentarze -_-

1

Moim zdaniem mozna tam umieszczac jakąś prostą walidacje, jakies mechanizmy do notyfikacji o zmianie stanu obiektu itd

Min0s napisał(a):

Lepiej tak:

int sprawdz (int co) {
	int wynik=0;
	if (co>5)
		wynik=10;
return wynik;
}
int liczba=0;
int Liczba {
	set {
		sprawdz(value);
	}
}

niż

int liczba=0;
int Liczba 
{
	set {
	if (value>5)
		liczba=10;
return liczba;
	}
}

?

Lepszy jest pierwszy sposób, bo oddziela logike walidacji od zmiany stanu obiektu. Wtedy mozesz np wszystkie walidacje przeniesc do osobnej klasy albo nawet biblioteki i przy drobnej zmianie (np z 10 na 9) nie musisz przegladnac pierdyliona miejsc

1

Zatem co najlepiej umieszczać w get/set?

Generalnie zasady są takie:
SET - ustawia wartość właściwości. Obiekt wewnętrznie może ustawiać wartości innych pól, ale raczej w uzasadnionych przypadkach. W żadnym wypadku użycie nie może powodować zmian stanów innych obiektów (zewnętrznych), ani wywoływać jakichś operacji biznesowych, powodować wymiany pakietów sieciowych, albo zapisów na dysk. Takie rzeczy wywołuje się metodami.

GET - zwraca wartość właściwości. Może być przeliczana na bieżąco, ale nie powinna się tworzyć np. nowa tablica czy nowy obiekt, bo częste wywoływanie może powodować problemy wydajnościowe. I tak jak w przypadku SET nie może być logiki, bo jej wywołanie jest w kodzie wówczas niejawne.

0

Czyli jednak nie trzeba rezygnować z logiki w set/get?
A wywoływanie metod w set/get?

1

No jakos ta walidacje trzeba odpalic, nie?:)

2
Min0s napisał(a):

Czyli jednak nie trzeba rezygnować z logiki w set/get?
A wywoływanie metod w set/get?

czyli trzeba uzywac zdrowego rozsadku. skoro skladnia jezyka dopuszcza taka mozliwosc to znaczy ze mozna tak robic, czy warto to juz inna kwestia, ja odradzam bo sie wiele razy na tym przejechalam. nie widze powodu zeby do gettera/settera wrzucac jakakolwiek logike, chyba ze lenistwo (ktore potem procentuje w czasie zmarnowanym na szukanie bugow i/lub refaktoring).
walidacja w setterach brzmi bardzo zachecajaco, jednak co gdy warunki nie przechodza i leci wyjatek albo logowane jest cos do pliku albo jeszcze lepiej do bazy danych? jest to troche glupawe gdy przy skladni wygladajacej jak przypisanie wartosci jednej do drugiej moze sie stac pierdyliard roznych rzeczy.
jak dla mnie naturalnym jest ze gdy widze kod typu

point.X = -1;

to nie ma prawa mi sie to wywalic ani powodowac zadnych side effectow, co innego gdy robie point.TrySetX(-1);

 gdzie od razu wiem ze musze miec sie na bacznosci.
0

Jak może wyglądać dobra walidacja w get/set? Chciałbym chociaż prosty kod.

1
if(argument jest nie spoko)
{
    throw new ArgumentException("tu opis problemu");
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1