Czy zagwarantowanie niezmienności jest uzasadnieniem ujawnienia pola zamiast właściwości?

10

Ogólne wskazówki dla C # to zawsze używać właściwości w polu publicznym. Ma to sens - wystawiając pole, ujawniasz wiele szczegółów implementacji. Za pomocą właściwości hermetyzujesz ten szczegół, aby był ukryty przed użyciem kodu, a zmiany implementacyjne są oddzielone od zmian interfejsu.

Zastanawiam się jednak, czy czasami występuje ważny wyjątek od tej reguły w przypadku readonlysłowa kluczowego. Stosując to słowo kluczowe w polu publicznym, dajesz dodatkową gwarancję: niezmienność. Nie jest to tylko szczegół implementacji, niezmienność jest czymś, co może zainteresować konsumenta. Użycie readonlypola powoduje, że staje się ono częścią kontraktu publicznego i czymś, co nie może zostać zerwane przez przyszłe zmiany lub dziedziczenie bez konieczności modyfikacji interfejsu publicznego. Tego nie może zaoferować nieruchomość.

Czy readonlyw niektórych przypadkach zagwarantowanie niezmienności jest uzasadnionym powodem wyboru pola zamiast nieruchomości?

(Dla wyjaśnienia, z pewnością nie mówię, że zawsze powinieneś dokonywać tego wyboru tylko dlatego, że pole jest w tej chwili niezmienne, tylko wtedy, gdy ma to sens w ramach projektu klasy i zamierzone jest użycie niezmienności w umowie. Najbardziej interesują mnie odpowiedzi skupiające się na tym, czy może to być uzasadnione, a nie na konkretnych przypadkach, w których tak nie jest, na przykład gdy potrzebujesz członka interfacelub chcesz leniwie ładować.)

Ben Aaronson
źródło
1
@gnat Aby wyjaśnić, używając „ReadOnly Property” używają terminologii VB.NET, co oznacza właściwość bez settera, a nie pole tylko do odczytu. Na potrzeby mojego pytania dwie opcje, które porównuje to pytanie, są równoważne - obie wykorzystują właściwości.
Ben Aaronson

Odpowiedzi:

6

Publiczne statyczne pola tylko do odczytu są z pewnością w porządku. Są zalecane w niektórych sytuacjach, na przykład gdy chcesz mieć nazwany stały obiekt, ale nie możesz użyć constsłowa kluczowego.

Pola członków tylko do odczytu są nieco trudniejsze. Nie ma dużej przewagi uzyskanej nad nieruchomością bez publicznego setera, a jest to poważna wada: pola nie mogą być członkami interfejsów. Jeśli więc zdecydujesz się przefakturować kod, aby działał w oparciu o interfejs zamiast konkretnego typu, będziesz musiał zmienić pola tylko do odczytu na właściwości z publicznymi modułami pobierającymi.

Publiczna nie-wirtualna właściwość bez chronionego obiektu ustawiającego zapewnia ochronę przed dziedziczeniem, chyba że odziedziczone klasy mają dostęp do pola zaplecza. Możesz całkowicie egzekwować ten kontrakt w klasie podstawowej.

Brak możliwości zmiany „niezmienności” członka bez zmiany publicznego interfejsu klasy nie stanowi w tym przypadku dużej bariery. Zwykle, jeśli chcesz zmienić pole publiczne na właściwość, przebiega to płynnie, z tym wyjątkiem, że masz do czynienia z dwoma przypadkami:

  1. Wszystko, co zapisuje element członkowski tego obiektu, takie jak: object.Location.X = 4jest możliwe tylko, jeśli Locationjest polem. Nie dotyczy to jednak pola tylko do odczytu, ponieważ nie można modyfikować struktury tylko do odczytu. (Zakłada się, że Locationjest to typ wartości - jeśli nie, to i tak problem nie miałby zastosowania, ponieważ readonlynie chroni przed tego rodzaju rzeczami.)
  2. Każde wywołanie metody, która przekazuje wartość jako outlub ref. Ponownie, nie jest to istotne dla pola tylko do odczytu, bo outi refparametry mają tendencję do modyfikacji, i to jest błąd kompilatora i tak przekazać wartość readonly jako OUT lub nr ref.

Innymi słowy, chociaż konwersja pola tylko do odczytu na właściwość tylko do odczytu narusza zgodność binarną, inny kod źródłowy musiałby zostać ponownie skompilowany bez żadnych zmian w celu obsłużenia tej zmiany. Dzięki temu gwarancja jest naprawdę łatwa do złamania.

Nie widzę żadnych korzyści dla readonlypola członka, a niemożność posiadania czegoś takiego w interfejsie jest dla mnie na tyle niekorzystna, że ​​nie używałbym tego.

Erik
źródło
Z odsetkami, dlaczego publiczne statyczny readonly pól w porządku? Czy tylko dlatego, że wykluczenie metod instancji usuwa większość przypadków użycia właściwości w niezmiennych polach (takich jak liczenie trafień, leniwe ładowanie itp.)?
Ben Aaronson,
Zasadnicza wada publicznego pola tylko do odczytu w porównaniu z właściwością tylko do odczytu zniknęła - elementy statyczne nie mogą być częścią interfejsów, więc mają podobne podstawy. Właściwość statyczna tylko do odczytu wymaga pamięci, która zostanie zainicjowana, lub musi odbudowywać wartość zwracaną za każdym razem, gdy zostanie wywołana, natomiast pole tylko do odczytu będzie miało prostszą składnię i zostanie zainicjowane przez konstruktora statycznego. Myślę więc, że publiczne statyczne pole tylko do odczytu ma potencjał do lepszej optymalizacji przez JIT bez żadnych wad, które normalnie byś miał od wystawienia pola.
Erik
2

Z mojego doświadczenia readonlywynika , że słowo kluczowe nie jest magią, którą obiecuje.

Na przykład masz klasę, w której konstruktor był prosty. Biorąc pod uwagę użycie (oraz fakt, że niektóre właściwości / pola klasy są niezmienne po budowie) możesz pomyśleć, że to nie ma znaczenia, więc użyłeś readonlysłowa kluczowego.

Później klasa staje się bardziej złożona, podobnie jak jej konstruktor. (Powiedzmy, że dzieje się tak w projekcie, który jest albo eksperymentalny, albo ma wystarczająco dużą prędkość zmniejszania zakresu / kontroli, ale musisz jakoś sprawić, żeby działał.) Przekonasz się, że pola, które readonlymożna modyfikować tylko wewnątrz konstruktora - ty nie można go modyfikować metodami, nawet jeśli metoda ta jest wywoływana tylko z konstruktora.

To techniczne ograniczenie zostało potwierdzone przez projektantów C # i może zostać naprawione w przyszłej wersji. Ale dopóki nie zostanie naprawiony, użycie readonlyjest bardziej restrykcyjne i może sprzyjać złemu stylowi kodowania (umieszczenie wszystkiego w metodzie konstruktora).

Dla przypomnienia, ani readonlywłasność niepublicznego podmiotu ustalającego nie daje żadnej gwarancji „w pełni zainicjowanego obiektu”. Innymi słowy, to projektant klasy decyduje, co oznacza „w pełni zainicjowany” i jak chronić ją przed przypadkowym użyciem, a taka ochrona na ogół nie jest niezawodna, co oznacza, że ​​ktoś może to obejść.

rwong
źródło
1
Wolę zachować prostotę konstruktorów - patrz brendan.enrick.com/post/… , blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple, więc w rzeczywistości zachęca do dobrego stylu kodowania
AlexFoxGill
1
Konstruktor może przekazać readonlypole jako parametr outlub refdo innych metod, które będą mogły dowolnie modyfikować według własnego uznania.
supercat
1

Pola są ZAWSZE szczegółami implementacji. Nie chcesz ujawniać szczegółów implementacji.

Podstawową zasadą inżynierii oprogramowania jest to, że nie wiemy, jakie będą wymagania w przyszłości. Chociaż twoje readonlypole może być dziś dobre, nic nie sugeruje, że będzie to część przyszłych wymagań. Co jeśli jutro zaistnieje potrzeba zaimplementowania licznika trafień, aby ustalić, ile razy dostęp do tego pola jest uzyskiwany? Co zrobić, jeśli chcesz zezwolić podklasom na zastąpienie pola?

Właściwości są tak łatwe w użyciu i są o wiele bardziej elastyczne niż pola publiczne, że nie mogę myśleć o pojedynczym przypadku użycia, w którym wybrałbym c # jako mój język i używałbym publicznych pól tylko do odczytu w klasie. Gdyby wydajność była tak niska, że ​​nie mogłem skorzystać z dodatkowego wywołania metody, prawdopodobnie użyłbym innego języka.

Tylko dlatego, że można zrobić coś z języka nie oznacza, że należy coś zrobić z językiem.

Zastanawiam się, czy projektanci języków żałują, że można upublicznić pola. Nie pamiętam, kiedy ostatni raz zastanawiałem się nad użyciem niepublicznych pól publicznych w moim kodzie.

Stephen
źródło
1
Rozumiem znaczenie budowania elastyczności w przyszłości, ale na pewno, jeśli napiszę taką klasę, jak, powiedzmy, Rationalz publicznością, niezmiennymi Numeratori Denominatorczłonkami, mogę być bardzo pewny, że ci członkowie nie będą wymagali leniwego ładowania lub licznika trafień lub podobny?
Ben Aaronson,
Ale czy możesz być pewien, że implementacja korzystająca z floattypów dla Numeratori Denominatornie będzie wymagać zmiany doubles?
Stephen
1
Cóż, nie, zdecydowanie nie powinny być floatani double. Powinny być int, longlub BigInteger. Być może ci muszą się zmienić ... ale jeśli tak, to musieliby również zmienić interfejs publiczny, więc tak naprawdę używanie właściwości nie dodaje hermetyzacji wokół tego szczegółu.
Ben Aaronson,
-3

Nie. To nie jest uzasadnienie.

Używanie readonly jako gwarancji niezmienności bez uzasadnienia jest bardziej jak hack.

Tylko do odczytu oznacza, że ​​wartość jest przypisywana przez konstruktor o, gdy zmienna jest zdefiniowana.

Myślę, że to będzie zła praktyka

Jeśli naprawdę chcesz zagwarantować niezmienność, skorzystaj z interfejsu, który ma więcej zalet niż wad.

Przykład prostego interfejsu: wprowadź opis zdjęcia tutaj

Pablo Granados
źródło
1
„użyj interfejsu” Jak?
Ben Aaronson,