Kiedy należy używać struct, a nie klasy w C #? Mój model koncepcyjny polega na tym, że struktury są używane w czasach, gdy element jest jedynie zbiorem typów wartości . Sposób na logiczne połączenie ich wszystkich w spójną całość.
Natknąłem się na te zasady tutaj :
- Struktura powinna reprezentować pojedynczą wartość.
- Struktura powinna mieć ślad w pamięci mniejszy niż 16 bajtów.
- Struktury nie należy zmieniać po utworzeniu.
Czy te zasady działają? Co semantycznie oznacza struct?
System.Drawing.Rectangle
narusza wszystkie trzy z tych zasad.Odpowiedzi:
Źródło, do którego odwołuje się OP, ma pewną wiarygodność ... ale co z Microsoftem - jakie jest stanowisko w sprawie korzystania z struct? Szukałem dodatkowej wiedzy od Microsoftu i oto, co znalazłem:
Microsoft konsekwentnie narusza te reguły
Dobra, w każdym razie # 2 i # 3. Nasz ukochany słownik ma 2 struktury wewnętrzne:
* Źródło odniesienia
Źródło „JonnyCantCode.com” otrzymało 3 z 4 - całkiem wybaczalne, ponieważ numer 4 prawdopodobnie nie byłby problemem. Jeśli znajdziesz się w boksie struktury, przemyśl swoją architekturę.
Zobaczmy, dlaczego Microsoft użyje tych struktur:
Entry
iEnumerator
reprezentuje pojedyncze wartości.Entry
nigdy nie jest przekazywany jako parametr poza klasą Dictionary. Dalsze dochodzenie pokazuje, że aby spełnić implementację IEnumerable, Dictionary używaEnumerator
struktury, którą kopiuje za każdym razem, gdy żądany jest moduł wyliczający ... ma sens.Enumerator
jest publiczny, ponieważ słownik jest policzalny i musi mieć równy dostęp do implementacji interfejsu IEnumerator - np. getter IEnumerator.Aktualizacja - Ponadto należy pamiętać, że gdy struct implementuje interfejs - podobnie jak moduł Enumerator - i jest rzutowany na ten zaimplementowany typ, struct staje się typem referencyjnym i zostaje przeniesiony na stertę. Wewnętrzny do słownika klasie Enumerator jest jeszcze typ wartości. Jednak natychmiast po wywołaniu metody zwracany jest
GetEnumerator()
typ odwołaniaIEnumerator
.To, czego nie widzimy tutaj, to jakakolwiek próba lub dowód na to, że struktury muszą być niezmienne lub utrzymywać rozmiar instancji tylko 16 bajtów lub mniej:
readonly
- nie niezmiennaEntry
ma nieokreślony czas (odAdd()
doRemove()
,Clear()
lub zbieranie śmieci);I ... 4. Obie struktury przechowują TKey i TValue, o których wszyscy wiemy, że mogą być typami referencyjnymi (dodane informacje o bonusie)
Niezależnie od zaszyfrowanych kluczy, słowniki są częściowo szybkie, ponieważ utworzenie struktury jest szybsze niż typ odwołania. Tutaj mam
Dictionary<int, int>
300 000 losowych liczb całkowitych z sekwencyjnie zwiększanymi kluczami.Pojemność : liczba elementów dostępnych przed zmianą wewnętrznej tablicy.
MemSize : ustalany przez serializację słownika do MemoryStream i uzyskanie długości bajtu (wystarczająco dokładnej do naszych celów).
Completed Resize : czas potrzebny na zmianę rozmiaru tablicy wewnętrznej z 150862 elementów na 312874 elementów. Kiedy zorientujesz się, że każdy element jest kolejno kopiowany
Array.CopyTo()
, nie jest to zbyt nędzne.Całkowity czas do wypełnienia : wprawdzie wypaczony z powodu logowania i
OnResize
wydarzenia, które dodałem do źródła; jednak nadal imponujące, aby wypełnić 300 000 liczb całkowitych, zmieniając rozmiar 15 razy podczas operacji. Z ciekawości, jaki byłby całkowity czas na wypełnienie, gdybym już znał pojemność? 13msA co teraz, jeśli
Entry
byłaby klasa? Czy te czasy lub wskaźniki naprawdę tak bardzo się różnią?Oczywiście dużą różnicą jest zmiana rozmiaru. Czy jest jakaś różnica, jeśli słownik zostanie zainicjowany przez pojemność? Za mało, żeby przejmować się ... 12ms .
Dzieje się tak, ponieważ
Entry
jest strukturą, nie wymaga inicjalizacji jak typ odwołania. Jest to zarówno piękno, jak i zmora typu wartości. Aby użyćEntry
jako typu odniesienia, musiałem wstawić następujący kod:Powód, dla którego musiałem zainicjować każdy element tablicy
Entry
jako typ odwołania, można znaleźć w witrynie MSDN: Structure Design . W skrócie:Jest to właściwie dość proste i zapożyczymy z Trzech praw robotyki Asimova :
... co bierzemy z tego : krótko mówiąc, bądź odpowiedzialny za stosowanie typów wartości. Są szybkie i wydajne, ale mogą powodować wiele nieoczekiwanych zachowań, jeśli nie są odpowiednio utrzymywane (tj. Niezamierzone kopie).
źródło
Decimal
lubDateTime
], to jeśli nie przestrzegałaby pozostałych trzech reguł, powinna zostać zastąpiona klasą. Jeśli struktura zawiera ustaloną kolekcję zmiennych, z których każda może zawierać dowolną wartość, która byłaby poprawna dla jej typu [np.Rectangle
], Wówczas powinna przestrzegać innych reguł, z których niektóre są sprzeczne z tymi dla struktur „pojedynczej wartości” .Dictionary
typ wpisu na podstawie tego, że jest to typ wewnętrzny, wydajność uznawano za ważniejszą od semantyki lub inną wymówkę. Chodzi mi o to, że typ typuRectangle
powinien mieć swoją zawartość ujawnioną jako pola edytowalne indywidualnie, nie „ponieważ” korzyści wynikające z wydajności przeważają nad wynikowymi niedoskonałościami semantycznymi, ale ponieważ typ semantycznie reprezentuje stały zestaw niezależnych wartości , a zatem zmienna struktura jest zarówno bardziej wydajny i semantycznie lepszy .Ilekroć nie potrzebujesz polimorfizmu, potrzebujesz semantyki wartości i chcesz uniknąć przydziału sterty i związanego z nią narzutu śmieci. Zastrzeżeniem jest jednak to, że struktury (dowolnie duże) są droższe do przekazania niż odwołania do klas (zwykle jedno słowo maszynowe), więc w praktyce klasy mogą być szybsze.
źródło
(Guid)null
(można rzucić zero na typ odniesienia), między innymi.Nie zgadzam się z zasadami podanymi w oryginalnym poście. Oto moje zasady:
1) Używasz struktur do wydajności, gdy są przechowywane w tablicach. (patrz także: Kiedy struktury są odpowiedzią? )
2) Potrzebujesz ich w kodzie przekazującym dane strukturalne do / z C / C ++
3) Nie używaj struktur, chyba że są potrzebne:
źródło
struct
wiedzieć, jak się zachowa, ale jeśli coś jeststruct
z odkrytymi polami, to wszystko, co musisz wiedzieć. Jeśli obiekt ujawnia właściwość typu narażonego pola-struktury, a jeśli kod odczytuje tę strukturę do zmiennej i modyfikuje, można bezpiecznie przewidzieć, że takie działanie nie wpłynie na obiekt, którego właściwość została odczytana, chyba że lub do momentu napisania struktury plecy. Natomiast, jeżeli nieruchomość była zmienny typ klasy, czyta je i modyfikując może zaktualizować podstawowy obiekt zgodnie z oczekiwaniami, ale ...Użyj struktury, jeśli chcesz wartościować semantykę, a nie semantykę odniesienia.
Edytować
Nie jestem pewien, dlaczego ludzie tak głoszą głosowanie, ale jest to słuszna uwaga, która została postawiona przed op wyjaśnieniem jego pytania i jest to najbardziej podstawowy podstawowy powód struktury.
Jeśli potrzebujesz semantyki odniesienia, potrzebujesz klasy, a nie struktury.
źródło
Oprócz odpowiedzi „jest to wartość”, jednym konkretnym scenariuszem korzystania ze struktur jest, gdy wiesz , że masz zestaw danych, który powoduje problemy z odśmiecaniem, i masz wiele obiektów. Na przykład duża lista / tablica instancji Person. Naturalną metaforą jest tutaj klasa, ale jeśli masz dużą liczbę długo żyjących instancji Osoby, mogą one w efekcie zapchać GEN-2 i spowodować utknięcie GC. Jeśli scenariusz to uzasadnia, jednym z potencjalnych podejść jest użycie tablicy (nie listy) struktur Person , tj
Person[]
. Teraz, zamiast mieć miliony obiektów w GEN-2, masz pojedynczą porcję na LOH (zakładam, że nie ma tutaj ciągów znaków itp. - tj. Czysta wartość bez żadnych odniesień). Ma to bardzo niewielki wpływ na GC.Praca z tymi danymi jest niewygodna, ponieważ dane są prawdopodobnie zbyt duże dla struktury i nie chcesz przez cały czas kopiować grubych wartości. Jednak dostęp do niego bezpośrednio w tablicy nie kopiuje struktury - jest on na miejscu (w przeciwieństwie do indeksu listy, który kopiuje). Oznacza to dużo pracy z indeksami:
Zauważ, że utrzymanie samych wartości niezmiennych pomoże tutaj. Aby uzyskać bardziej złożoną logikę, użyj metody z parametrem by-ref:
Znów jest to na miejscu - nie skopiowaliśmy wartości.
W bardzo szczególnych scenariuszach ta taktyka może być bardzo skuteczna; jest to jednak dość zaawansowany scernario, którego należy próbować tylko wtedy, gdy wiesz, co robisz i dlaczego. Domyślnie tutaj byłaby klasa.
źródło
List
Wydaje mi się, że używaArray
scen zakulisowych. nie?Ze specyfikacji języka C # :
Alternatywą jest uczynienie z Point struktury.
Teraz tworzony jest tylko jeden obiekt - ten dla tablicy - a instancje Point są przechowywane w tablicy w linii.
Konstruktory struktur są wywoływane z nowym operatorem, ale nie oznacza to, że pamięć jest przydzielana. Zamiast dynamicznie alokować obiekt i zwracać do niego odwołanie, konstruktor struktury po prostu zwraca samą wartość struktury (zwykle w tymczasowej lokalizacji na stosie), a następnie ta wartość jest następnie kopiowana w razie potrzeby.
W przypadku klas dwie zmienne mogą odwoływać się do tego samego obiektu, a zatem operacje na jednej zmiennej mogą wpływać na obiekt, do którego odwołuje się druga zmienna. W przypadku struktur każda zmienna ma własną kopię danych i operacje na jednym z nich nie mają wpływu na drugi. Na przykład wynik generowany przez następujący fragment kodu zależy od tego, czy Point jest klasą, czy strukturą.
Jeśli Point jest klasą, wynikiem jest 20, ponieważ aib odnoszą się do tego samego obiektu. Jeśli Point jest strukturą, wynikiem jest 10, ponieważ przypisanie a do b tworzy kopię wartości, a na tę kopię nie ma wpływu późniejsze przypisanie do ax
Poprzedni przykład podkreśla dwa ograniczenia struktur. Po pierwsze, kopiowanie całej struktury jest zwykle mniej wydajne niż kopiowanie odwołania do obiektu, więc przypisanie i przekazanie parametrów wartości może być droższe w przypadku struktur niż w przypadku typów odniesienia. Po drugie, z wyjątkiem parametrów ref i out, nie można tworzyć odwołań do struktur, co wyklucza ich użycie w wielu sytuacjach.
źródło
ref
mutowalnej strukturze i wiedzieć, że wszelkie mutacje, które wykona na niej metoda zewnętrzna, zostaną wykonane przed jej powrotem. Szkoda, że .net nie ma pojęcia o efemerycznych parametrach i zwracanych wartościach funkcji, ponieważ ...ref
za pomocą obiektów klasy. Zasadniczo zmienne lokalne, parametry i zwracane wartości funkcji mogą być trwałe (domyślne), zwrotne lub efemeryczne. Kod miałby zakaz kopiowania efemerycznych rzeczy do czegokolwiek, co wykraczałoby poza obecny zakres. Rzeczy zwrotne byłyby niczym efemeryczne, z wyjątkiem tego, że mogłyby zostać zwrócone z funkcji. Zwracana wartość funkcji byłaby ograniczona najostrzejszymi ograniczeniami mającymi zastosowanie do któregokolwiek z jej parametrów „zwrotnych”.Struktury są dobre do atomowej reprezentacji danych, przy czym dane te mogą być wielokrotnie kopiowane przez kod. Klonowanie obiektu jest na ogół droższe niż kopiowanie struktury, ponieważ wymaga alokacji pamięci, uruchomienia konstruktora i dezalokacji / wyrzucania elementów bezużytecznych.
źródło
Oto podstawowa zasada.
Jeśli wszystkie pola składowe są typami wartości, utwórz strukturę .
Jeśli jedno pole członka jest typem odniesienia, utwórz klasę . Jest tak, ponieważ pole typu odwołania i tak będzie wymagało przydziału sterty.
Przykłady
źródło
string
są semantycznie równoważne wartościom, a przechowywanie referencji do niezmiennego obiektu w polu nie pociąga za sobą przydziału sterty. Różnica między strukturą z odsłoniętymi polami publicznymi a obiektem klasy z odsłoniętymi polami publicznymi polega na tym, że biorąc pod uwagę sekwencję koduvar q=p; p.X=4; q.X=5;
,p.X
będzie miała wartość 4, jeślia
jest typem struktury, i 5, jeśli jest to typ klasy. Jeśli ktoś chce mieć możliwość wygodnej modyfikacji elementów tego typu, powinien wybrać „klasę” lub „strukturę” w oparciu o to, czy chceq
wpłynąć na zmianyp
.ArraySegment<T>
zawiera w sobie typT[]
, który zawsze jest typem klasy. Typ strukturyKeyValuePair<TKey,TValue>
jest często używany z typami klas jako parametry ogólne.Po pierwsze: scenariusze interakcji lub gdy musisz określić układ pamięci
Po drugie: kiedy dane mają prawie taki sam rozmiar jak wskaźnik odniesienia.
źródło
Musisz użyć „struct” w sytuacjach, w których chcesz jawnie określić układ pamięci za pomocą StructLayoutAttribute - zazwyczaj dla PInvoke.
Edycja: Komentarz wskazuje, że możesz używać klasy lub struktury za pomocą StructLayoutAttribute i to z pewnością prawda. W praktyce zwykle używasz struktury - jest ona przypisywana do stosu w stosunku do stosu, co ma sens, jeśli przekazujesz argument do niezarządzanego wywołania metody.
źródło
Używam struktur do pakowania lub rozpakowywania dowolnego binarnego formatu komunikacji. Obejmuje to odczyt lub zapis na dysk, listy wierzchołków DirectX, protokoły sieciowe lub postępowanie z zaszyfrowanymi / skompresowanymi danymi.
Trzy wymienione przez ciebie wytyczne nie były dla mnie przydatne w tym kontekście. Kiedy muszę wypisać czterysta bajtów rzeczy w określonym porządku, zdefiniuję czterysta bajtową strukturę i wypełnię ją wszelkimi niepowiązanymi wartościami, które powinien mieć, i idę ustawić w dowolny sposób, jaki jest najbardziej sensowny w danym momencie. (Okej, czterysta bajtów byłoby dość dziwnych - ale kiedy pisałem do życia pliki Excela, miałem do czynienia ze strukturami do około czterdziestu bajtów, ponieważ tak duże są niektóre rekordy BIFF.)
źródło
Z wyjątkiem typów wartości, które są używane bezpośrednio przez środowisko wykonawcze i różnych innych do celów PInvoke, należy używać typów wartości tylko w 2 scenariuszach.
źródło
this
parametru używanego do wywoływania jego metod); klasy pozwalają na powielanie referencji..NET obsługuje
value types
ireference types
(w Javie można zdefiniować tylko typy referencyjne). Wystąpieniareference types
get przydzielane są na zarządzanej stercie i są usuwane, gdy nie ma zaległych odwołań do nich.value types
Z drugiej strony instancje są alokowane wstack
pamięci, a zatem przydzielona pamięć jest odzyskiwana, gdy tylko kończy się ich zakres. I oczywiścievalue types
mijamy według wartości ireference types
referencji. Wszystkie pierwotne typy danych C #, z wyjątkiem System.String, są typami wartości.Kiedy używać struct ponad klasą,
W C #
structs
sąvalue types
klasyreference types
. Możesz tworzyć typy wartości w C #, używającenum
słowa kluczowego istruct
słowa kluczowego. Użycievalue type
zamiast zamiastreference type
spowoduje zmniejszenie liczby obiektów na zarządzanej stercie, co spowoduje mniejsze obciążenie modułu wyrzucania elementów bezużytecznych (GC), rzadsze cykle GC, aw konsekwencji lepszą wydajność. Miej jednakvalue types
również swoje wady. Przekazanie dużegostruct
jest zdecydowanie droższe niż przekazanie referencji, to jeden oczywisty problem. Innym problemem jest narzut związany zboxing/unboxing
. Jeśli zastanawiasz się, co toboxing/unboxing
znaczy, skorzystaj z tych linków, aby uzyskać dobre wyjaśnienie na tematboxing
iunboxing
. Oprócz wydajności zdarzają się sytuacje, gdy po prostu potrzebujesz typów, aby mieć semantykę wartości, co byłoby bardzo trudne (lub brzydkie) do wdrożenia, jeślireference types
wszystko, co masz. Powinieneś używaćvalue types
tylko, gdy potrzebujesz skopiować semantykę lub potrzebujesz automatycznej inicjalizacji, zwykle warrays
tych typach.źródło
ref
. Przechodząc przez dowolną strukturę wielkościref
kosztami jest takie samo jak przekazywanie referencji klasy pod względem wartości. Kopiowanie dowolnej struktury wielkości lub przekazywanie według wartości jest tańsze niż wykonywanie kopii obronnej obiektu klasy i przechowywanie lub przekazywanie odniesienia do tego. Wielkie czasy klasy są lepsze niż struktury do przechowywania wartości (1), gdy klasy są niezmienne (aby uniknąć kopiowania obronnego), a każda utworzona instancja będzie dużo przekazywana, lub ...readOnlyStruct.someMember = 5;
nie jest utworzeniesomeMember
właściwości tylko do odczytu, ale zamiast tego uczynienie z niej pola.Struct jest typ wartości. Jeśli przypiszesz strukturę do nowej zmiennej, nowa zmienna będzie zawierać kopię oryginału.
Wykonanie następujących wyników powoduje 5 wystąpień struktury przechowywanej w pamięci:
Klasa jest typem odniesienia. Gdy przypisujesz klasę do nowej zmiennej, zmienna zawiera odwołanie do oryginalnego obiektu klasy.
Wykonanie poniższych wyników powoduje tylko jedno wystąpienie obiektu klasy w pamięci.
Struct s mogą zwiększać prawdopodobieństwo błędu kodu. Jeśli obiekt wartości jest traktowany jak zmienny obiekt odniesienia, programista może być zaskoczony, gdy wprowadzone zmiany zostaną nieoczekiwanie utracone.
źródło
Zrobiłem mały test porównawczy z BenchmarkDotNet aby lepiej zrozumieć korzyści „strukturalne” w liczbach. Testuję zapętlanie tablicy (lub listy) struktur (lub klas). Tworzenie tych tablic lub list nie wchodzi w zakres testu porównawczego - jasne jest, że „klasa” jest bardziej obciążona, zużyje więcej pamięci i będzie wymagała GC.
Wniosek jest następujący: bądź ostrożny z LINQ i ukrytymi strukturami do boksowania / rozpakowywania i używanie struktur do mikrooptymalizacji ściśle trzymaj się tablic.
PS Kolejnym punktem odniesienia dla przekazywania struktury / klasy przez stos wywołań jest https://stackoverflow.com/a/47864451/506147
Kod:
źródło
Typy struktur w języku C # lub innych językach .net są zwykle używane do przechowywania rzeczy, które powinny zachowywać się jak grupy wartości o stałej wielkości. Przydatnym aspektem typów struktur jest to, że pola instancji typu struktury można modyfikować, modyfikując miejsce przechowywania, w którym są przechowywane, i w żaden inny sposób. Możliwe jest kodowanie struktury w taki sposób, że jedynym sposobem na zmutowanie dowolnego pola jest skonstruowanie zupełnie nowej instancji, a następnie użycie przypisania struktury do mutacji wszystkich pól obiektu docelowego poprzez zastąpienie ich wartościami z nowej instancji, ale chyba że struct nie daje możliwości utworzenia instancji, w której jego pola mają wartości inne niż domyślne, wszystkie jego pola będą podlegały mutacji, jeśli i jeśli sama struktura jest przechowywana w zmiennej lokalizacji.
Zauważ, że możliwe jest zaprojektowanie typu struktury tak, aby zasadniczo zachowywał się jak typ klasy, jeśli struktura zawiera prywatne pole typu klasy i przekierowuje własne elementy do owiniętego obiektu klasy. Na przykład,
PersonCollection
właściwości oferują mocSortedByName
iSortedById
, z których oba przytrzymaj „niezmienny” odniesienie doPersonCollection
(ustalony w ich konstruktora) i wdrożyćGetEnumerator
dzwoniąc albocreator.GetNameSortedEnumerator
albocreator.GetIdSortedEnumerator
. Takie struktury zachowywałyby się jak odniesienia do aPersonCollection
, z tym wyjątkiem, że ichGetEnumerator
metody byłyby powiązane z różnymi metodami wPersonCollection
. Można również mieć strukturę owijającą część tablicy (np. Można zdefiniowaćArrayRange<T>
strukturę, która będzie zawieraćT[]
wywołaneArr
, intOffset
i intLength
, z indeksowaną właściwością, do której dostęp miałby dostęp dla indeksuidx
z zakresu od 0 do ). Niestety, jeśli taka struktura jest przeznaczona tylko do odczytu, bieżące wersje kompilatora nie zezwalają na takie operacje, ponieważ nie mają możliwości ustalenia, czy takie operacje będą próbowały zapisać w polach .Length-1
Arr[idx+Offset]
foo
foo[3]+=4;
foo
Możliwe jest również zaprojektowanie struktury tak, aby zachowywała się jak typ wartości, który zawiera kolekcję o zmiennej wielkości (która będzie wyglądać, jakby była kopiowana za każdym razem, gdy jest w strukturze), ale jedynym sposobem na wykonanie tej pracy jest upewnienie się, że żaden obiekt, do którego struct posiada odnośnik, który będzie kiedykolwiek wystawiony na wszystko, co może go zmutować. Na przykład, można mieć strukturę tablicową, która przechowuje tablicę prywatną i której zindeksowana metoda „put” tworzy nową tablicę, której zawartość jest podobna do oryginalnej z wyjątkiem jednego zmienionego elementu. Niestety sprawienie, by takie struktury działały wydajnie, może być nieco trudne. Chociaż zdarza się, że semantyka struct może być wygodna (np. Możliwość przekazania kolekcji podobnej do tablicy do procedury, wywołujący i odbierający wiedzą, że zewnętrzny kod nie zmodyfikuje kolekcji,
źródło
Nie - nie do końca zgadzam się z zasadami. Są to dobre wytyczne do rozważenia przy wydajności i standaryzacji, ale nie w świetle możliwości.
Jak widać w odpowiedziach, istnieje wiele kreatywnych sposobów ich wykorzystania. Dlatego te wytyczne muszą być takie, zawsze ze względu na wydajność i wydajność.
W tym przypadku używam klas do reprezentowania obiektów świata rzeczywistego w ich większej formie, używam struktur do reprezentowania mniejszych obiektów, które mają bardziej dokładne zastosowania. Sposób, w jaki to powiedziałeś, „bardziej spójna całość”. Słowo kluczowe jest spójne. Klasy będą bardziej obiektowymi elementami, podczas gdy struktury mogą mieć niektóre z tych cech, choć w mniejszej skali. IMO.
Używam ich często w znacznikach Treeview i Listview, gdzie bardzo szybko można uzyskać dostęp do wspólnych atrybutów statycznych. Zawsze starałem się uzyskać te informacje w inny sposób. Na przykład w moich aplikacjach bazodanowych korzystam z widoku drzewa, w którym mam tabele, SP, funkcje lub dowolne inne obiekty. Tworzę i wypełniam moją strukturę, umieszczam ją w tagu, wyciągam, pobieram dane zaznaczenia i tak dalej. Nie zrobiłbym tego z klasą!
Staram się, aby były małe, używam ich w sytuacjach pojedynczych i powstrzymuję je przed zmianą. Rozsądnie jest pamiętać o pamięci, przydziale i wydajności. Testowanie jest tak konieczne.
źródło
double
wartości dla tych współrzędnych, taka specyfikacja zmusiłaby go do zachowują się semantycznie identycznie do struktury pola narażonego, z wyjątkiem niektórych szczegółów zachowania wielowątkowego (klasa niezmienna byłaby lepsza w niektórych przypadkach, podczas gdy struktura pola narażonego byłaby lepsza w innych; tak zwana „struktura niezmienna” byłaby gorsze w każdym przypadku).Moją zasadą jest
1, Zawsze używaj klasy;
2, jeśli występuje jakikolwiek problem z wydajnością, próbuję zmienić klasę na struct w zależności od reguł wymienionych przez @IAbstract, a następnie wykonuję test, aby sprawdzić, czy zmiany te mogą poprawić wydajność.
źródło
Foo
zawierała stały zbiór niezależnych wartości (np. Współrzędnych punktu), które czasem chce się przekazywać jako grupa, a czasem chce się zmieniać niezależnie. Nie znalazłem żadnego wzorca używania klas, który łączy oba cele prawie tak ładnie, jak zwykła struktura pola narażonego (która, jako stały zbiór niezależnych zmiennych, idealnie pasuje do rachunku).public readonly
pola w moich typach, ponieważ tworzenie właściwości tylko do odczytu to po prostu zbyt dużo pracy, praktycznie bez żadnych korzyści.)MyListOfPoint[3].Offset(2,3);
się kompilatora wvar temp=MyListOfPoint[3]; temp.Offset(2,3);
transformację, która jest nieprawdziwa po zastosowaniu ...Offset
metody. Właściwym sposobem zapobiegania takiemu fałszywemu kodowi nie powinno być uczynienie struktur niepotrzebnie niezmiennymi, lecz umożliwienie metodom takim jakOffset
oznaczanie atrybutem zabraniającym wspomnianej transformacji. Również domniemane konwersje liczbowe mogłyby być znacznie lepsze, gdyby można je było oznaczyć, aby można je było stosować tylko w przypadkach, w których ich wywołanie byłoby oczywiste. Jeśli istnieją przeciążenia dlafoo(float,float)
ifoo(double,double)
, przypuszczam, że próba użycia afloat
idouble
często nie powinna stosować niejawnej konwersji, ale powinna być błędem.double
wartości dofloat
lub przekazanie jej do metody, która może przyjąćfloat
argument, ale niedouble
, prawie zawsze zrobiłoby to, co zamierzał programista. Natomiast przypisywaniefloat
wyrażeniadouble
bez jawnego typowania jest często błędem. Jedynym czasem, w którym możliwa jest niejawnadouble->float
konwersja, byłyby problemy, gdy spowodowałoby to wybranie niecałkowitego przeciążenia. Sądzę, że właściwym sposobem, aby temu zapobiec, nie powinno być zakazywanie implcit double-> float, ale oznaczanie przeciążeń atrybutami, aby uniemożliwić konwersję.Klasa jest typem referencyjnym. Kiedy tworzony jest obiekt klasy, zmienna, do której obiekt jest przypisany, zawiera tylko odwołanie do tej pamięci. Gdy odwołanie do obiektu jest przypisane do nowej zmiennej, nowa zmienna odnosi się do oryginalnego obiektu. Zmiany dokonane za pomocą jednej zmiennej są odzwierciedlone w drugiej zmiennej, ponieważ obie odnoszą się do tych samych danych. Struktura jest typem wartości. Po utworzeniu struktury zmienna, do której przypisana jest struktura, przechowuje rzeczywiste dane struktury. Kiedy struct jest przypisane do nowej zmiennej, jest kopiowane. Nowa zmienna i pierwotna zmienna zawierają zatem dwie osobne kopie tych samych danych. Zmiany wprowadzone w jednej kopii nie wpływają na drugą kopię. Zasadniczo klasy są używane do modelowania bardziej złożonych zachowań lub danych, które mają zostać zmodyfikowane po utworzeniu obiektu klasy.
Klasy i struktury (Podręcznik programowania w języku C #)
źródło
MIT # 1: STRUKTY TO LEKKIE ZAJĘCIA
Ten mit ma różne formy. Niektóre osoby uważają, że typy wartości nie mogą lub nie powinny mieć metod lub innych istotnych zachowań - powinny być używane jako proste typy przesyłania danych, z tylko polami publicznymi lub prostymi właściwościami. Typ DateTime jest dobrym kontrprzykładem: ma sens, aby był typem wartości, ponieważ jest podstawową jednostką, taką jak liczba lub znak, a także ma sens, aby móc wykonywać obliczenia na podstawie jego wartość. Patrząc na to z drugiej strony, typy przesyłania danych i tak często powinny być typami referencyjnymi - decyzja powinna opierać się na pożądanej wartości lub semantyce typu referencyjnego, a nie na prostocie tego typu. Inne osoby uważają, że typy wartości są „lżejsze” niż typy referencyjne pod względem wydajności. Prawda jest taka, że w niektórych przypadkach typy wartości są bardziej wydajne - nie wymagają wyrzucania elementów bezużytecznych, chyba że są zapakowane w pudełka, nie mają narzutu identyfikacji typu i nie wymagają na przykład dereferencji. Ale pod innymi względami typy referencyjne są bardziej wydajne - przekazywanie parametrów, przypisywanie wartości do zmiennych, zwracanie wartości i podobne operacje wymagają tylko 4 lub 8 bajtów do wywołania (w zależności od tego, czy korzystasz z 32-bitowego czy 64-bitowego CLR ) zamiast kopiowania wszystkich danych. Wyobraź sobie, że ArrayList jest w jakiś sposób „czystym” typem wartości, a przekazanie wyrażenia ArrayList do metody wymaga skopiowania wszystkich danych! W prawie wszystkich przypadkach wydajność nie jest tak naprawdę determinowana przez tego rodzaju decyzję. Wąskie gardła prawie nigdy nie są tam, gdzie sądzisz, a zanim podejmiesz decyzję dotyczącą projektu na podstawie wydajności, powinieneś zmierzyć różne opcje. Warto zauważyć, że połączenie dwóch przekonań również nie działa. Nie ma znaczenia, ile metod ma typ (bez względu na to, czy jest to klasa, czy struktura) - nie ma to wpływu na pamięć zajmowaną przez instancję. (Koszt jest związany z pamięcią zajmowaną przez sam kod, ale jest on ponoszony raz, a nie dla każdej instancji).
MIT 2: RODZAJE ODNIESIENIA ŻYJĄ W STOSUNKU; RODZAJE WARTOŚCI ŻYJĄ NA STOSOWIE
Często przyczyną jest lenistwo osoby powtarzającej się. Pierwsza część jest poprawna - instancja typu odwołania jest zawsze tworzona na stercie. To druga część, która powoduje problemy. Jak już zauważyłem, wartość zmiennej istnieje wszędzie tam, gdzie jest zadeklarowana, więc jeśli masz klasę ze zmienną instancji typu int, wartość tej zmiennej dla dowolnego obiektu zawsze będzie znajdować się tam, gdzie reszta danych dla tego obiektu - na stosie. Na stosie znajdują się tylko zmienne lokalne (zmienne zadeklarowane w ramach metod) i parametry metody. W C # 2 i późniejszych nawet niektóre zmienne lokalne tak naprawdę nie znajdują się na stosie, co zobaczysz, gdy przyjrzymy się anonimowym metodom w rozdziale 5. CZY TE POJĘCIA SĄ WŁAŚCIWE? Można argumentować, że jeśli piszesz kod zarządzany, powinieneś pozwolić środowisku wykonawczemu martwić się o to, jak najlepiej wykorzystać pamięć. W rzeczy samej, specyfikacja języka nie gwarantuje, co tam mieszka; przyszły środowisko wykonawcze może być w stanie utworzyć niektóre obiekty na stosie, jeśli wie, że może się z tym uwolnić, lub kompilator C # może wygenerować kod, który prawie nie używa stosu. Następny mit to zwykle tylko kwestia terminologii.
MIT 3: OBIEKTY SĄ PRZEDSTAWIONE PRZEZ ODNIESIENIE W C # BY DOMYŚLNIE
To prawdopodobnie najbardziej rozpowszechniony mit. Znów ludzie, którzy twierdzą to często (choć nie zawsze), wiedzą, jak naprawdę zachowuje się C #, ale nie wiedzą, co tak naprawdę oznacza „przekazanie przez odniesienie”. Niestety jest to mylące dla osób, które wiedzą, co to znaczy. Formalna definicja przekazywania przez referencję jest względnie skomplikowana i obejmuje wartości l i podobną terminologię informatyczną, ale ważne jest to, że jeśli przekazujesz zmienną przez referencję, metoda, którą wywołujesz, może zmienić wartość zmiennej wywołującej zmieniając wartość parametru. Pamiętajmy, że wartością zmiennej typu odwołania jest odwołanie, a nie sam obiekt. Można zmienić zawartość obiektu, do którego odnosi się parametr, bez przekazywania samego parametru przez odwołanie. Na przykład,
Po wywołaniu tej metody wartość parametru (odwołanie do StringBuilder) jest przekazywana przez wartość. Jeśli miałbyś zmienić wartość zmiennej konstruktora w ramach metody - na przykład za pomocą instrukcji builder = null; - zmiana ta nie byłaby widoczna dla wywołującego, w przeciwieństwie do mitu. Warto zauważyć, że nie tylko „odniesienie” do mitu jest niedokładne, ale także „przekazane przedmioty”. Same obiekty nigdy nie są przekazywane ani przez odwołanie, ani przez wartość. Gdy dotyczy typu odwołania, zmienna jest przekazywana przez odwołanie lub wartość argumentu (odwołanie) jest przekazywana przez wartość. Oprócz czegokolwiek innego, odpowiada to na pytanie, co się stanie, gdy wartość pusta zostanie użyta jako argument wartości - gdyby przekazano obiekty, spowodowałoby to problemy, ponieważ nie byłoby obiektu do przekazania! Zamiast, odwołanie zerowe jest przekazywane przez wartość w taki sam sposób, jak każde inne odwołanie. Jeśli to szybkie wyjaśnienie wprawiło Cię w osłupienie, możesz zajrzeć do mojego artykułu „Parametr przekazujący w C #” (http://mng.bz/otVt ), która zawiera więcej szczegółów. Te mity nie są jedynymi. Boks i rozpakowanie przychodzą z powodu ich uczciwego nieporozumienia, które postaram się wyjaśnić w następnej kolejności.
Odniesienie: C # w szczegółach 3. edycja autorstwa Jona Skeeta
źródło
Myślę, że dobrym pierwszym przybliżeniem jest „nigdy”.
Myślę, że dobrym drugim przybliżeniem jest „nigdy”.
Jeśli desperacko pragniesz perf, rozważ je, ale zawsze mierz.
źródło
Miałem właśnie do czynienia z Nazwaną potokiem Windows Communication Foundation [WCF] i zauważyłem, że warto używać Structów, aby upewnić się, że wymiana danych ma charakter wartości zamiast referencyjnego .
źródło
Struktura C # jest lekką alternatywą dla klasy. Może robić prawie to samo co klasa, ale tańsze jest używanie struktury zamiast klasy. Przyczyna tego jest nieco techniczna, ale podsumowując, nowe wystąpienia klasy są umieszczane na stercie, gdzie nowo tworzone struktury są umieszczane na stosie. Co więcej, nie masz do czynienia z odniesieniami do struktur, jak z klasami, ale zamiast tego pracujesz bezpośrednio z instancją struktury. Oznacza to również, że gdy przekazujesz strukturę do funkcji, ma ona wartość, a nie odwołanie. Więcej na ten temat w rozdziale dotyczącym parametrów funkcji.
Dlatego powinieneś używać struktur, gdy chcesz reprezentować prostsze struktury danych, a zwłaszcza, jeśli wiesz, że utworzysz wiele z nich. Istnieje wiele przykładów w .NET Framework, w których Microsoft używał struktur zamiast klas, na przykład struktury Point, Rectangle i Color.
źródło
Struct może być użyty do poprawy wydajności odśmiecania. Chociaż zwykle nie musisz się martwić wydajnością GC, istnieją scenariusze, w których może to być zabójca. Jak duże pamięci podręczne w aplikacjach o niskim opóźnieniu. Zobacz ten post jako przykład:
http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/
źródło
Typy struktury lub wartości można zastosować w następujących scenariuszach -
Więcej informacji na temat typów wartości i typów wartości można znaleźć tutaj, pod tym linkiem
źródło
W skrócie, użyj struct, jeśli:
1- właściwości / pola obiektu nie muszą być zmieniane. Mam na myśli, że chcesz tylko nadać im wartość początkową, a następnie je przeczytać.
2- właściwości i pola w twoim obiekcie są typem wartości i nie są tak duże.
W takim przypadku możesz skorzystać z struktur dla lepszej wydajności i zoptymalizowanego przydziału pamięci, ponieważ używają one tylko stosów, a nie zarówno stosów, jak i stosów (w klasach)
źródło
Rzadko używam struktury do różnych rzeczy. Ale to tylko ja. Zależy to od tego, czy potrzebuję, aby obiekt był zerowalny, czy nie.
Jak stwierdzono w innych odpowiedziach, używam klas dla obiektów w świecie rzeczywistym. Mam również sposób myślenia o strukturach używanych do przechowywania niewielkich ilości danych.
źródło
Struktury są pod wieloma względami podobne do klas / obiektów. Struktura może zawierać funkcje, elementy i może być dziedziczona. Ale struktury są w C # używane tylko do przechowywania danych . Struktury zajmują mniej pamięci RAM niż klasy i są łatwiejsze do zebrania przez śmieciarz . Ale kiedy używasz funkcji w swojej strukturze, kompilator faktycznie przyjmuje tę strukturę bardzo podobnie do klasy / obiektu, więc jeśli chcesz czegoś z funkcjami, użyj klasy / obiektu .
źródło