Prawidłowe użycie właściwości, a pola prywatne - get i set

1

Witam!
Niedawno natknąłem się na zapętlenie obsługi get dla jednej z właściwości.
Właściwości rozumiałem dotychczas tak, że teraz zamiast pisania osobnych metod:

class Entity
{
    public int GetValue() { return value; }
    public void SetValue(int value) { this.value = value; }

    private int value;
}

mogę napisać po prostu:

class Entity
{
    public int Value
    {
        get { return Value; }
        set { Value = value; }
    }
}

I jest to czyste, fajne, proste i intuicyjne.
Niestety takie rozwiązanie powoduje, że dowolne wywołanie

Entity entity = new Entity();
entity.Value = 1;

zapetli wywołanie set i skończy się przepełnieniem stosu.
Dlaczego tak się dzieje jest oczywiste: przypisanie woła set, w którym mamy znów set i rekurencja bez wyjścia.

Żeby uniknąć takiego rozwiązania implementujemy to więc tak:

class Entity
{
    public int Value
    {
        get { return _Value; }
        set { _Value = value; }
    }

    private int _Value;
}

i chociaż problemu zapętlenia teraz nie ma, to jednak właśnie tutaj dochodzimy do mojego pytania:

Czy naprawdę właśnie tak trzeba to implementować?
Moim zdaniem takie rozwiązanie jest już mniej czytelne, niż pisanie własnych GetValue(). Mając _Value i Value kod staje się raczej mniej czytelny, może bardziej podatny na błędy (przynajmniej we wnętrzu klasy).

Najwidoczniej czegoś nie widzę lub nie rozumiem. Jak więc podejść do pisania { get; set; }? Jak to rozwiązać, jakie są praktyki by stosowanie właściwości było wygodne i czytelne?

3

no... { get; * set; } jest wygodne i czytelne, ale czasem jak chcesz zrobić coś bardziej skomplikowanego, to robisz pole _Coś, którego abstrakcją jest property Coś.

Czy naprawdę właśnie tak trzeba to implementować?

Idąc twoim pomysłem i gdyby tam nie wywoływało się to rekurencyjnie, to jak sądzisz, co się stanie / co powinno się stać w takim przypadku?

class Entity
{
	public int Age
	{
		get { return Age; }
		set
		{
			if (value < 0)
				throw new Exception("wtf");

			Age = value;
		}
	}

	public void SetAgeAsCEO(int age)
	{
		Console.WriteLine("hack explained in PR #21301 that resolves bug #430751");
		this.Age = age;
	}
}

var ent = new Entity();
ent.SetAgeAsCEO(-5);

Tracimy możliwość ustawienia Age z ręki, nawet wewnątrz klasy. A może to zaleta? :)

1

Napisałem prostą aplikację konsolową:

class Program
    {
        static void Main(string[] args)
        {
            SomeClass some = new SomeClass();
            some.Dupa = true;

            Console.WriteLine(some.Dupa.ToString());

            Console.ReadKey();
        }
    }

    public class SomeClass
    {
        public bool Dupa { get; set; }
    }

Output:

True

i nie wywala mi stosu.

1

Chyba jesteś bardzo poczatkującym programistą C# bo niebardzo rozumiem z czym masz problem . Właściwość to są dwie ukryte metody get i set , ktróre generuje kompilator C# . Właściwości przypisują wartości polom klas. Właściwość automatyczna - oprócz metod kompilator tworzy jeszcze prywatne pole. Do tego prywatnego pola nie możesz się dostać z poziomu języka C#.
Nie wiem jeszcze po jakiego grzyba nazywasz pole klasy tak samo jak parametr metody dostępowej i musisz przez to jawnie pisać this.

0

@WeiXiao: Właśnie do tego zmierzam - idąc moim pomysłem zadziała to następująco:
po linijce 18 wywołany zostanie set w linijce 6, w którym warunek nie będzie spełniony i wciąż będzie 0.
Sprawdziłem, że wywołanie

    public void SetAgeAsCEO(int age)
    {
        Console.WriteLine("hack explained in PR #21301 that resolves bug #430751");
        this.Age = age;
    }

wywoła nam set dla tej właściwości, czyli to co chcemy bo warunek ujemnego wieku nadal będzie sprawdzony.
Byłoby więc wszystko OK, gdyby nie ta rekurencja...

Dlatego właśnie pytam:
Czy rozwiązanie

robisz pole _Coś, którego abstrakcją jest property Coś.

nie psuje czytelności? Moim zdaniem trochę tak, dlatego pytam jak tego uniknąć? A może się nie da?

0

Tylko, że wtedy nie masz możliwości manipulowania wartością poza Setterem, nawet od wewnątrz (chcąc ominąć logikę Settera)

Raczej logika powinna weryfikować wartości z zewnątrz, bo od wewnątrz raczej wiemy co robimy.

3

OK, to jest tak.

class Entity
{
    public int Value
    {
        get { return Value; }
        set { Value = value; }
    }
}

NIE WOLNO Ci tak zrobić. No bo jaki będzie efekt? Jeśli robisz:

int i = entity.Value;

wywołujesz getter, czyli: return Value. I znowu wywołujesz getter: return Value... i tak aż do przepełnienia stosu. Innymi słowy - w getterze właściwości wywołujesz gettera tej właściwości. Pojawia się nieskończona rekurencja i w pewnym momencie dostajesz przepełnienie stosu.

Masz dwie inne opcje:

int theValue;

public int Value
{
  get {return theValue;}
  set {theValue = value; }
}

To jest pierwsza opcja, która daje Ci maksimum kontroli. Polega to na tym, że tworzysz sobie jawnie prywatne pole (tzw. backup field), w którym jawnie przechowujesz wartość. Wartość jest zwracana przez gettera właściwości i ustawiana za pomocą settera właściwości. W tej klasie może być też ustawiana bezpośrednio i czasem trzeba tak robić - ale zawsze należy to dobrze przemyśleć.

Jako, że dokładnie taka konstrukcja była bardzo często stosowana przez wiele lat (taka, czyli zwracanie / ustawianie wartości z backup field bez żadnej logiki), twórcy języka w końcu wprowadzili pewne ułatwienie: automatic properties:

public int Value { get; set; }

Taka definicja jest swego rodzaju skrótem. I w języku maszynowym w efekcie dostaniesz dokładnie to samo, co w przykładzie wyżej. Czyli kompilator niejawnie utworzy Ci backup field i będzie się nim posługiwał. Różnica jest tylko taka, że Ty nie masz bezpośredniego dostępu do backup field i posługujesz się tu tylko właściwością.

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