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:
struct PointyStruct {public int x, y, z;};
klasa PointyClass {public int x, y, z;};
void Method1 (PointyStruct foo);
void Method2 (ref PointyStruct foo);
void Method3 (PointyClass foo);
Dla każdej metody odpowiedz na następujące pytania:
- Zakładając, że metoda nie używa żadnego „niebezpiecznego” kodu, czy mogłaby modyfikować foo?
- Jeśli przed wywołaniem metody nie istnieją żadne zewnętrzne odniesienia do „foo”, czy może istnieć zewnętrzne odniesienie po?
Odpowiedzi:
Pytanie 1:
Method1()
nie (jasne zamiary)
Method2()
: tak (jasne zamiary)
Method3()
: tak (niepewne zamiary)
Pytanie 2
Method1()
:: nie
Method2()
: nie (chyba że niebezpieczne)
Method3()
: tak
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.
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.