Po dyskusjach tutaj na temat SO przeczytałem już kilkakrotnie uwagę, że struktury modyfikowalne są „złe” (jak w odpowiedzi na to pytanie ).
Jaki jest rzeczywisty problem ze zmiennością i strukturami w C #?
c#
struct
immutability
mutable
Dirk Vollmar
źródło
źródło
int
s,bool
s, a wszystkie inne typy wartości są złe. Istnieją przypadki mutacji i niezmienności. Przypadki te zależą od roli danych, a nie od rodzaju alokacji / udostępniania pamięci.int
i niebool
można ich.
-Syntax, dzięki czemu operacje z danymi o zmienionym typie i danymi o wartości zmienionej wyglądają tak samo, chociaż są wyraźnie różne. Jest to wina właściwości C #, a nie struktur - niektóre języki oferują alternatywnąa[V][X] = 3.14
składnię do mutowania w miejscu. W języku C # lepiej byłoby zaoferować metody mutatora typu struct-member, takie jak „MutateV (mutator <ref Vector2>)” i używać go w podobny sposóba.MutateV((v) => { v.X = 3; })
(przykład jest nadmiernie uproszczony ze względu na ograniczenia C # dotycząceref
słowa kluczowego, ale z pewnymi obejścia powinny być możliwe) .x
) ta pojedyncza operacja to 4 instrukcje:ldloc.0
(ładuje zmienną 0-indeksową do ...T
jest typem. Ref jest tylko słowem kluczowym, które powoduje, że zmienna jest przekazywana do samej metody, a nie jej kopia. Ma to również sens dla typów referencji, ponieważ możemy zmienić zmienną , tj. Referencja poza metodą będzie wskazywała na inny obiekt po zmianie w ramach metody. Ponieważref T
nie jest typem, ale sposobem przekazywania parametru metody, nie można go wprowadzić<>
, ponieważ można tam umieścić tylko typy. Więc to jest po prostu nieprawidłowe. Może byłoby to wygodne, może zespół C # mógłby to zrobić dla nowej wersji, ale teraz pracują nad niektórymi ...Odpowiedzi:
Struktury są typami wartości, co oznacza, że są kopiowane, gdy są przekazywane.
Więc jeśli zmienisz kopię, zmienisz tylko tę kopię, a nie oryginał, a nie inne, które mogą być w pobliżu.
Jeśli twoja struktura jest niezmienna, wszystkie automatyczne kopie wynikające z przekazania wartości będą takie same.
Jeśli chcesz to zmienić, musisz to zrobić świadomie, tworząc nową instancję struktury ze zmodyfikowanymi danymi. (nie kopia)
źródło
Od czego zacząć ;-p
Blog Erica Lipperta jest zawsze dobry na cytat:
Po pierwsze, dość łatwo tracisz zmiany ... na przykład, wyciągając rzeczy z listy:
co to zmieniło? Nic użytecznego ...
To samo z właściwościami:
zmuszając cię do zrobienia:
mniej krytycznie pojawia się problem z rozmiarem; obiekty zmienne mają zwykle wiele właściwości; ale jeśli masz strukturę z dwoma
int
s, astring
, aDateTime
i abool
, możesz bardzo szybko wypalić dużo pamięci. Dzięki klasie wielu dzwoniących może udostępnić odwołanie do tego samego wystąpienia (odwołania są małe).źródło
++
operatora. W takim przypadku kompilator po prostu zapisuje jawne przypisanie zamiast krzątania się programisty.Nie powiedziałbym, że zło, ale zmienność jest często oznaką nadgorliwości ze strony programisty, aby zapewnić maksimum funkcjonalności. W rzeczywistości często nie jest to potrzebne, a to z kolei sprawia, że interfejs jest mniejszy, łatwiejszy w użyciu i trudniejszy w użyciu (= bardziej niezawodny).
Jednym z przykładów są konflikty odczytu / zapisu i zapisu / zapisu w warunkach wyścigu. Po prostu nie mogą wystąpić w niezmiennych strukturach, ponieważ zapis nie jest prawidłową operacją.
Ponadto twierdzę, że zmienność prawie nigdy nie jest potrzebna , programista po prostu myśli , że może to być w przyszłości. Na przykład zmiana daty nie ma sensu. Zamiast tego utwórz nową datę na podstawie starej. Jest to tania operacja, więc wydajność nie jest brana pod uwagę.
źródło
float
składników transformacji graficznej. Jeśli taka metoda zwraca strukturę pola z odsłoniętymi sześcioma komponentami, oczywiste jest, że modyfikacja pól struktury nie zmodyfikuje obiektu graficznego, z którego została otrzymana. Jeśli taka metoda zwraca zmienny obiekt klasy, być może zmiana jego właściwości zmieni podstawowy obiekt graficzny, a może nie - nikt tak naprawdę nie wie.Zmienne struktury nie są złe.
Są absolutnie niezbędne w warunkach wysokiej wydajności. Na przykład, gdy linie pamięci podręcznej lub zbieranie śmieci stają się wąskim gardłem.
Nie nazwałbym użycia niezmiennej struktury w tych doskonale uzasadnionych przypadkach użycia „złem”.
Widzę, że punkt składnia C # 's nie pomaga odróżnić dostęp członka typu wartości lub typu referencyjnego, więc jestem wszystko dla preferujących niezmienne konstrukcjom, które egzekwować niezmienność, przez zmienny konstrukcjom.
Jednak zamiast po prostu oznaczać niezmienne struktury jako „złe”, radziłbym objąć ten język i opowiadać się za bardziej pomocną i konstruktywną zasadą.
Na przykład: „struktury są typami wartości, które są kopiowane domyślnie. Potrzebujesz odniesienia, jeśli nie chcesz ich kopiować” lub „najpierw spróbuj pracować z strukturami tylko do odczytu” .
źródło
struct
publiczne), niż zdefiniować klasę, której można użyć niezdarnie, aby osiągnąć te same cele lub dodać wiązkę śmieci do struktury, aby emulować taką klasę (zamiast mieć ją zachowują się jak zestaw zmiennych sklejonych razem z taśmą klejącą, a tego naprawdę się chce)Struktury z publicznymi zmiennymi polami lub właściwościami nie są złe.
Metody struktur (w odróżnieniu od ustawiaczy właściwości), które mutują „to”, są nieco złe, tylko dlatego, że .net nie zapewnia sposobu ich odróżnienia od metod, które tego nie robią. Metody struktur, które nie mutują „tego”, powinny być wywoływalne nawet na strukturach tylko do odczytu, bez potrzeby defensywnego kopiowania. Metody mutujące „to” nie powinny być w ogóle wywoływalne w strukturach tylko do odczytu. Ponieważ .net nie chce zabraniać wywoływania metod struktur, które nie modyfikują „tego” przed strukturami tylko do odczytu, ale nie chce zezwalać na mutowanie struktur tylko do odczytu, defensywnie kopiuje struktury w trybie do odczytu tylko konteksty, prawdopodobnie najgorsze z obu światów.
Jednak pomimo problemów z obsługą metod auto-mutacji w kontekstach tylko do odczytu, zmienne struktury często oferują semantykę znacznie przewyższającą modyfikowalne typy klas. Rozważ następujące trzy sygnatury metod:
Dla każdej metody odpowiedz na następujące pytania:
Odpowiedzi:
Metoda 1 nie może modyfikować foo i nigdy nie otrzymuje referencji. Metoda 2 pobiera krótkotrwałe odwołanie do foo, którego może użyć modyfikować pola foo dowolną liczbę razy, w dowolnej kolejności, dopóki nie zwróci, ale nie może zachować tego odwołania. Przed powrotem metody Method2, chyba że użyje niebezpiecznego kodu, wszelkie kopie, które mogły zostać wykonane z jej odwołania „foo”, znikną. Method3, w przeciwieństwie do Method2, otrzymuje obiecująco współdzielone odniesienie do foo i nie wiadomo, co może z tym zrobić. Może w ogóle nie zmienić foo, może zmienić foo, a następnie powrócić lub może odwoływać się do foo do innego wątku, który może mutować go w dowolny arbitralny sposób w dowolnym przyszłym czasie.
Tablice struktur oferują cudowną semantykę. Biorąc pod uwagę RectArray [500] typu Rectangle, jest jasne i oczywiste, jak np. Skopiować element 123 do elementu 456, a następnie jakiś czas później ustawić szerokość elementu 123 do 555, bez zakłócania elementu 456. "RectArray [432] = RectArray [321 ]; ...; RectArray [123] .Width = 555; ". Wiedząc, że Prostokąt jest strukturą z polem całkowitym o nazwie Szerokość, powiesz wszystko, co musisz wiedzieć o powyższych instrukcjach.
Załóżmy teraz, że RectClass była klasą z tymi samymi polami co Rectangle, a jedna chciała wykonać te same operacje na RectClassArray [500] typu RectClass. Być może tablica ma pomieścić 500 wstępnie zainicjowanych niezmiennych odwołań do zmiennych obiektów RectClass. w takim przypadku właściwym kodem byłoby coś w rodzaju „RectClassArray [321] .SetBounds (RectClassArray [456]); ...; RectClassArray [321] .X = 555;”. Być może zakłada się, że tablica przechowuje instancje, które się nie zmienią, więc właściwy kod będzie bardziej podobny do: „RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = New RectClass (RectClassArray [321 ]); RectClassArray [321] .X = 555; " Aby wiedzieć, co należy zrobić, trzeba by dowiedzieć się znacznie więcej o RectClass (np. Czy obsługuje on konstruktor kopii, metodę kopiowania z itp.) ) i zamierzone użycie tablicy. Nigdzie nie jest tak czysty, jak użycie struktury.
Dla pewności nie istnieje żaden przyjemny sposób, by żadna klasa kontenerów inna niż tablica oferowała czystą semantykę tablicy struct. Najlepszą rzeczą, jaką można zrobić, jeśli chce się zaindeksować kolekcję za pomocą np. Łańcucha, byłoby prawdopodobnie zaoferowanie ogólnej metody „ActOnItem”, która zaakceptowałaby ciąg dla indeksu, ogólny parametr i delegata, który zostałby przekazany przez odniesienie zarówno do parametru ogólnego, jak i elementu kolekcji. Pozwoliłoby to na prawie taką samą semantykę jak tablice struktur, ale jeśli nie da się przekonać ludzi vb.net i C # do zaoferowania ładnej składni, kod będzie wyglądał niezgrabnie, nawet jeśli jest to dość wydajne (przekazanie parametru ogólnego pozwalają na użycie statycznego delegata i unikną potrzeby tworzenia tymczasowych instancji klasy).
Osobiście jestem zirytowany nienawiścią Erica Lipperta i in. wyrzut dotyczący zmiennych typów wartości. Oferują znacznie czystszą semantykę niż rozrzucone typy referencyjne, które są używane wszędzie. Pomimo niektórych ograniczeń związanych ze wsparciem .net dla typów wartości, istnieje wiele przypadków, w których zmienne typy wartości lepiej pasują niż jakikolwiek inny rodzaj bytu.
źródło
Rectangle
, mógłbym z łatwością wymyślić wspólną sytuację, w której masz bardzo niejasne zachowanie. Weź pod uwagę, że WinForms implementuje zmiennyRectangle
typ używany we właściwości formularzaBounds
. Jeśli chcę zmienić granice, chciałbym użyć twojej ładnej składni:form.Bounds.X = 10;
to jednak nie zmienia dokładnie nic w formularzu (i generuje piękny błąd informujący o tym). Niespójność jest zmorą programowania i dlatego potrzebna jest niezmienność.Typy wartości zasadniczo reprezentują niezmienne pojęcia. Fx, nie ma sensu mieć wartości matematycznych, takich jak liczba całkowita, wektor itp., A następnie móc ją modyfikować. To by było jak przedefiniowanie znaczenia wartości. Zamiast zmieniać typ wartości, bardziej sensowne jest przypisanie innej unikalnej wartości. Pomyśl o tym, że typy wartości są porównywane poprzez porównanie wszystkich wartości jego właściwości. Chodzi o to, że jeśli właściwości są takie same, to jest to ta sama uniwersalna reprezentacja tej wartości.
Jak wspomina Konrad, zmiana daty nie ma sensu, ponieważ wartość reprezentuje ten unikalny punkt w czasie, a nie instancję obiektu czasu, który ma zależność od stanu lub kontekstu.
Mam nadzieję, że ma to dla ciebie jakiś sens. Z pewnością chodzi bardziej o koncepcję, którą próbujesz uchwycić za pomocą typów wartości, niż o praktyczne szczegóły.
źródło
int
iterator, który byłby całkowicie bezużyteczny, gdyby był niezmienny. Myślę, że łączysz kompilatory / implementacje typów wykonawczych „wartości” ze „zmiennymi wpisanymi do typu wartości” - ta ostatnia z pewnością może zostać zmieniona na dowolną z możliwych wartości.Istnieje kilka innych przypadków narożnych, które mogą prowadzić do nieprzewidzianych zachowań z punktu widzenia programisty.
Niezmienne typy wartości i pola tylko do odczytu
Zmienne typy wartości i tablica
Załóżmy, że mamy tablicę naszej
Mutable
struktury i wywołujemyIncrementI
metodę dla pierwszego elementu tej tablicy. Jakiego zachowania oczekujesz od tej rozmowy? Czy powinno to zmienić wartość tablicy, czy tylko kopię?Zmienne struktury nie są złe, o ile ty i reszta zespołu dobrze rozumiecie, co robicie. Ale jest zbyt wiele przypadków narożnych, w których zachowanie programu byłoby inne niż oczekiwano, co może prowadzić do subtelnych, trudnych do wyprodukowania i trudnych do zrozumienia błędów.
źródło
T[]
indeks i liczbę całkowitą oraz zapewnienie, że dostęp do właściwości typuArrayRef<T>
będzie interpretowany jako dostęp do odpowiedniego elementu tablicy) [jeśli klasa chciała ujawnićArrayRef<T>
dla dowolnego innego celu, mogłaby zapewnić metodę - w przeciwieństwie do właściwości - do jej odzyskania]. Niestety nie ma takich przepisów.Jeśli kiedykolwiek programowałeś w języku takim jak C / C ++, struktury mogą być używane jako zmienne. Przekaż je z ref, wokół i nic nie może pójść źle. Jedyny problem, jaki znalazłem, to ograniczenia kompilatora C # i, w niektórych przypadkach, nie jestem w stanie zmusić głupiej rzeczy do użycia odwołania do struktury zamiast do kopii (na przykład, gdy struct jest częścią klasy C # ).
Zmienne struktury nie są złe, C # uczynił je złymi. Cały czas używam zmiennych struktur w C ++ i są one bardzo wygodne i intuicyjne. W przeciwieństwie do C # zmusił mnie do całkowitego porzucenia struktur jako członków klas ze względu na sposób, w jaki obsługują one obiekty. Ich wygoda kosztowała nas naszą.
źródło
readonly
zastosuje, ale jeśli uniknie się tych rzeczy, pola klas typów struktur są w porządku. Jedynym naprawdę fundamentalnym ograniczeniem struktur jest to, że pole struktury typu klasy mutableint[]
może zawierać w sobie tożsamość lub niezmienny zestaw wartości, ale nie może być użyte do enkapsulacji wartości zmiennych bez zakapsułkowania niepożądanej tożsamości.Jeśli trzymasz się struktur, dla których są przeznaczone (w C #, Visual Basic 6, Pascal / Delphi, C ++ typu struktur (lub klas), gdy nie są one używane jako wskaźniki), przekonasz się, że struktura nie jest niczym więcej niż zmienną złożoną . Oznacza to: potraktujesz je jako spakowany zestaw zmiennych pod wspólną nazwą (zmienna rekordowa, z której odwołujesz się do członków).
Wiem, że wprowadzałoby to w błąd wielu ludzi głęboko przyzwyczajonych do OOP, ale to nie wystarczający powód, by twierdzić, że takie rzeczy są z natury złe, jeśli są właściwie stosowane. Niektóre struktury są niezmienne, jak zamierzają (tak jest w przypadku Pythona
namedtuple
), ale jest to kolejny paradygmat do rozważenia.Tak: struktury wymagają dużej ilości pamięci, ale nie będzie to więcej pamięci, wykonując:
w porównaniu do:
Zużycie pamięci będzie co najmniej takie samo lub nawet większe w przypadku niewymiennym (chociaż przypadek ten byłby tymczasowy, dla bieżącego stosu, w zależności od języka).
Wreszcie struktury to struktury , a nie obiekty. W POO główną właściwością obiektu jest jego tożsamość , która przez większość czasu jest nie większa niż adres pamięci. Struct oznacza strukturę danych (niepoprawny obiekt, więc i tak nie mają tożsamości), a dane można modyfikować. W innych językach record (zamiast struct , jak ma to miejsce w przypadku Pascala) jest słowem i ma ten sam cel: po prostu zmienna rekordu danych, przeznaczona do odczytu z plików, modyfikowana i zrzucana do plików (to jest główny używać, aw wielu językach można nawet zdefiniować wyrównanie danych w rekordzie, choć niekoniecznie tak jest w przypadku poprawnie nazywanych Obiektów).
Chcesz dobry przykład? Struktury służą do łatwego odczytu plików. Python ma tę bibliotekę, ponieważ ponieważ jest zorientowana obiektowo i nie obsługuje struktur, musiał zaimplementować ją w inny sposób, co jest nieco brzydkie. Języki implementujące struktury mają tę funkcję ... wbudowaną. Spróbuj odczytać nagłówek mapy bitowej z odpowiednią strukturą w językach takich jak Pascal lub C. Będzie to łatwe (jeśli struktura jest poprawnie zbudowana i wyrównana; w Pascal nie używałbyś dostępu opartego na rekordach, ale funkcje do odczytu dowolnych danych binarnych). Zatem w przypadku plików i bezpośredniego (lokalnego) dostępu do pamięci struktury są lepsze niż obiekty. Na dzień dzisiejszy jesteśmy przyzwyczajeni do JSON i XML, dlatego też zapominamy o użyciu plików binarnych (a jako efekt uboczny użycie struktur). Ale tak: istnieją i mają cel.
Nie są źli. Po prostu używaj ich we właściwym celu.
Jeśli myślisz w kategoriach młotów, będziesz chciał traktować śruby jak gwoździe, aby znaleźć śruby, które są trudniejsze do wbicia się w ścianę, i to będzie wina śrub, a one będą złymi.
źródło
Wyobraź sobie, że masz tablicę 1 000 000 struktur. Każda struktura reprezentuje kapitał z takimi rzeczami, jak bid_price, offer_price (być może dziesiętne) i tak dalej, to jest tworzone przez C # / VB.
Wyobraź sobie, że tablica jest tworzona w bloku pamięci przydzielonym w niezarządzanej stercie, aby jakiś inny natywny wątek kodu mógł jednocześnie uzyskiwać dostęp do tablicy (być może jakiś matematyczny kod o wysokiej wydajności).
Wyobraź sobie, że kod C # / VB nasłuchuje informacji rynkowej o zmianach cen, kod ten może mieć dostęp do jakiegoś elementu tablicy (dla dowolnego bezpieczeństwa), a następnie zmodyfikować niektóre pola ceny.
Wyobraź sobie, że robi się to dziesiątki, a nawet setki tysięcy razy na sekundę.
Cóż, spójrzmy prawdzie w oczy, w tym przypadku naprawdę chcemy, aby te struktury były zmienne, muszą być, ponieważ są udostępniane przez inny natywny kod, więc tworzenie kopii nie pomoże; muszą być, ponieważ tworzenie kopii około 120-bajtowej struktury przy tych szybkościach jest szaleństwem, szczególnie gdy aktualizacja może faktycznie wpłynąć tylko na jeden bajt lub dwa.
Hugo
źródło
Kiedy coś można zmutować, zyskuje poczucie tożsamości.
Ponieważ
Person
jest zmienna, bardziej naturalne jest myślenie o zmianie pozycji Erica niż klonowanie Erica, przenoszenie klonu i niszczenie oryginału . Obie operacje zmieniłyby zawartośćeric.position
, ale jedna jest bardziej intuicyjna niż druga. Podobnie, bardziej intuicyjne jest przekazywanie Ericowi (jako odniesienie) metod jego modyfikacji. Podanie metody klona Erica prawie zawsze będzie zaskakujące. Każdy, kto chce mutować,Person
musi pamiętać, aby poprosić o odniesienie doPerson
przeciwnym razie zrobi coś złego.Jeśli sprawisz, że typ będzie niezmienny, problem zniknie; jeśli nie mogę zmodyfikować
eric
, nie ma dla mnie znaczenia, czy otrzymam,eric
czy kloneric
. Mówiąc bardziej ogólnie, typ jest bezpieczny do przekazania przez wartość, jeśli cały jego obserwowalny stan jest utrzymywany w elementach, które są:Jeśli te warunki są spełnione, zmienna wartość typu zachowuje się jak typ odniesienia, ponieważ płytka kopia nadal pozwoli odbiornikowi modyfikować oryginalne dane.
Intuicyjność niezmiennego
Person
zależy jednak od tego, co próbujesz zrobić. JeśliPerson
tylko reprezentuje zestaw danych o osobie, nie ma w tym nic nieintuicyjnego;Person
zmienne naprawdę reprezentują wartości abstrakcyjne , a nie obiekty. (W takim przypadku prawdopodobnie lepiej byłoby zmienić nazwę naPersonData
.) JeśliPerson
faktycznie modeluje się samą osobę, pomysł ciągłego tworzenia i przenoszenia klonów jest głupi, nawet jeśli unikniesz pułapki myślenia, że modyfikujesz oryginalny. W takim przypadku prawdopodobnie bardziej naturalne byłoby po prostu utworzeniePerson
typu referencyjnego (to znaczy klasy).To prawda, że jak nauczyło nas programowanie funkcjonalne, korzyści z uczynienia wszystkiego niezmiennym (nikt nie może potajemnie trzymać się odniesienia do niego
eric
i mutować go), ale ponieważ nie jest to idiomatyczne w OOP, nadal będzie to nieintuicyjne dla każdego, kto pracuje z twoim kod.źródło
foo
posiada jedyne odniesienie do swojego celu w dowolnym miejscu we wszechświecie i nic nie uchwyciło wartości skrótu tożsamości tego obiektu, wówczas pole mutacjifoo.X
jest semantycznie równoważne zfoo
wskazywaniem na nowy obiekt, który jest podobny do tego, o którym wcześniej wspominał, ale zX
utrzymywanie pożądanej wartości. W przypadku typów klas generalnie trudno jest ustalić, czy istnieje wiele odwołań do czegoś, ale w przypadku struktur jest to łatwe: nie istnieją.Thing
jest typem klasy możliwej do zmodyfikowania, aThing[]
będzie hermetyzować tożsamość obiektu - czy tego się chce, czy nie - chyba że można zapewnić, że żadneThing
z elementów tablicy, do której istnieją odniesienia zewnętrzne, nigdy nie zostanie zmutowane. Jeśli nie chcemy, aby elementy tablicy zawierały tożsamość, należy ogólnie upewnić się, że żadne elementy, do których należą odniesienia, nigdy nie zostaną zmutowane, lub że żadne odniesienia zewnętrzne nigdy nie będą istnieć dla elementów, które posiada [podejścia hybrydowe mogą również działać ]. Żadne z tych podejść nie jest zbyt wygodne. JeśliThing
jest strukturą,Thing[]
hermetyzuje tylko wartości.Nie ma to nic wspólnego ze strukturami (i nie z C #), ale w Javie możesz mieć problemy ze zmiennymi obiektami, gdy są one np. Kluczami na mapie mieszającej. Jeśli zmienisz je po dodaniu ich do mapy i zmieni to kod skrótu , mogą się zdarzyć złe rzeczy.
źródło
Osobiście, kiedy patrzę na kod, wygląda mi to dość niezręcznie:
data.value.set (data.value.get () + 1);
zamiast po prostu
data.value ++; lub data.value = data.value + 1;
Hermetyzacja danych jest przydatna podczas przekazywania klasy i chcesz mieć pewność, że wartość zostanie zmodyfikowana w kontrolowany sposób. Jeśli jednak masz zestaw publiczny i dostajesz funkcje, które niewiele więcej niż ustawiają wartość tego, co kiedykolwiek zostało przekazane, jak to jest ulepszenie w stosunku do zwykłego przekazywania struktury danych publicznych?
Kiedy tworzę prywatną strukturę wewnątrz klasy, stworzyłem tę strukturę, aby zorganizować zestaw zmiennych w jedną grupę. Chcę móc modyfikować tę strukturę w zakresie klasy, nie otrzymywać kopii tej struktury i tworzyć nowych instancji.
Dla mnie to uniemożliwia prawidłowe użycie struktur używanych do organizowania zmiennych publicznych, gdybym chciał kontroli dostępu, użyłbym klasy.
źródło
Istnieje kilka problemów z przykładem Erica Lipperta. Pomyślono o tym, aby zilustrować punkt, w którym struktury są kopiowane i jak może to stanowić problem, jeśli nie będziesz ostrożny. Patrząc na ten przykład, widzę go jako wynik złego nawyku programowania, a nie tak naprawdę problemu z strukturą lub klasą.
Struktura ma mieć tylko członków publicznych i nie powinna wymagać żadnej enkapsulacji. Jeśli tak, to naprawdę powinien to być typ / klasa. Naprawdę nie potrzebujesz dwóch konstrukcji, aby powiedzieć to samo.
Jeśli masz klasę otaczającą strukturę, wywołaj metodę w klasie, aby zmutować strukturę członka. To właśnie zrobiłbym jako dobry nawyk programowania.
Prawidłowe wdrożenie byłoby następujące.
Wygląda na to, że jest to problem z nawykami programistycznymi, a nie problem z samą strukturą. Struktury powinny być zmienne, to jest idea i intencja.
Rezultat zmian voila zachowuje się zgodnie z oczekiwaniami:
1 2 3 Naciśnij dowolny klawisz, aby kontynuować. . .
źródło
Istnieje wiele zalet i wad zmiennych danych. Wadą za milion dolarów jest aliasing. Jeśli ta sama wartość jest używana w wielu miejscach, a jedna z nich ją zmienia, to wygląda na to, że magicznie zmieniła się na inne miejsca, które jej używają. Jest to związane, ale nie identyczne z warunkami wyścigu.
Zaletą miliona dolarów jest czasami modułowość. Zmienny stan pozwala ukryć zmieniające się informacje w kodzie, które nie muszą o tym wiedzieć.
Art of the Tłumacz ustny szczegółowo omawia te kompromisy i podaje kilka przykładów.
źródło
Nie wierzę, że są złe, jeśli są właściwie stosowane. Nie wstawiłbym tego do mojego kodu produkcyjnego, ale zrobiłbym coś w rodzaju próbnych testów strukturalnych, gdzie żywotność struktury jest stosunkowo niewielka.
Korzystając z przykładu Erica, być może chcesz utworzyć drugą instancję tego Erica, ale dokonaj korekt, ponieważ taka jest natura twojego testu (tj. Powielanie, a następnie modyfikowanie). Nie ma znaczenia, co stanie się z pierwszą instancją Erica, jeśli używamy Eric2 do pozostałej części skryptu testowego, chyba że planujesz użyć go jako porównania testowego.
Byłoby to szczególnie przydatne do testowania lub modyfikowania starszego kodu, który płytko definiuje określony obiekt (punkt struktur), ale dzięki niezmiennej strukturze zapobiega to irytującemu użyciu.
źródło
Range<T>
typ z elementamiMinimum
iMaximum
polami typuT
oraz kodemRange<double> myRange = foo.getRange();
, powinny pochodzić wszelkie gwarancje dotyczące tego, coMinimum
i coMaximum
zawierafoo.GetRange();
. BędącRange
strukturą pola narażonego wyjaśniłby, że nie doda żadnego własnego zachowania.