Jakie są Twoje ogólne zasady, kiedy używać struktur vs. klas? Zastanawiam się nad definicją tych terminów w języku C #, ale jeśli twój język ma podobne pojęcia, chciałbym również poznać Twoją opinię.
Zwykle używam klas do prawie wszystkiego i używam struktur tylko wtedy, gdy coś jest bardzo uproszczone i powinno być typem wartości, takim jak PhoneNumber lub coś takiego. Ale wydaje się to stosunkowo niewielkim zastosowaniem i mam nadzieję, że są bardziej interesujące przypadki użycia.
Odpowiedzi:
Zgodnie z ogólną zasadą struktury powinny być małymi, prostymi (jednopoziomowymi) kolekcjami powiązanych właściwości, które po utworzeniu są niezmienne; do wszystkiego innego użyj klasy.
C # jest fajny, ponieważ struktury i klasy nie mają wyraźnych różnic w deklaracji innych niż słowo kluczowe definiujące; więc jeśli uważasz, że musisz „uaktualnić” strukturę do klasy lub odwrotnie „obniżyć” klasę do struktury, jest to głównie prosta kwestia zmiany słowa kluczowego (istnieje kilka innych problemów; struktury nie mogą wyprowadzić z jakiejkolwiek innej klasy lub typu strukturalnego i nie mogą jawnie zdefiniować domyślnego konstruktora bez parametrów).
Mówię „głównie”, ponieważ ważniejszą rzeczą, jaką należy wiedzieć o strukturach, jest to, że ponieważ są one typami wartości, traktowanie ich jak klasy (typy referencyjne) może skończyć się bólem i połową. W szczególności modyfikowanie właściwości struktury może powodować nieoczekiwane zachowanie.
Załóżmy na przykład, że masz klasę SimpleClass z dwiema właściwościami, A i B. Tworzysz kopię tej klasy, inicjujesz A i B, a następnie przekazujesz instancję do innej metody. Ta metoda dodatkowo modyfikuje A i B. Z powrotem w funkcji wywołującej (tej, która utworzyła instancję), A i B instancji będą miały wartości podane im przez wywoływaną metodę.
Teraz uczynisz to strukturą. Właściwości są nadal zmienne. Wykonujesz te same operacje przy użyciu tej samej składni, co wcześniej, ale teraz nowe wartości A i B nie występują w instancji po wywołaniu metody. Co się stało? Twoja klasa jest teraz strukturą, co oznacza, że jest to typ wartości. Jeśli przekazujesz typ wartości do metody, domyślnym (bez słowa kluczowego out lub ref) jest przekazanie „według wartości”; płytka kopia instancji jest tworzona do użycia przez metodę, a następnie niszczona, gdy metoda jest wykonywana, pozostawiając pierwotną instancję nienaruszoną.
Staje się to jeszcze bardziej mylące, jeśli miałbyś mieć typ referencyjny jako członek swojej struktury (nie jest to zabronione, ale bardzo zła praktyka w praktycznie wszystkich przypadkach); klasa nie zostałaby sklonowana (tylko odwołanie do struktury), więc zmiany w strukturze nie wpłynęłyby na oryginalny obiekt, ale zmiany w podklasie struktury będą wpływać na instancję z kodu wywołującego. Może to bardzo łatwo wprowadzić struktury modyfikowalne w bardzo niespójne stany, które mogą powodować błędy daleko od miejsca, w którym znajduje się prawdziwy problem.
Z tego powodu praktycznie każdy autorytet w C # mówi, aby zawsze uczynić twoje struktury niezmiennymi; zezwalaj konsumentowi na określanie wartości właściwości tylko przy konstruowaniu obiektu i nigdy nie udostępniaj żadnych środków do zmiany wartości tego wystąpienia. Pola tylko do odczytu lub właściwości tylko do odczytu są regułą. Jeśli konsument chce zmienić wartość, może utworzyć nowy obiekt na podstawie wartości starego, z żądanymi zmianami, lub może wywołać metodę, która zrobi to samo. To zmusza ich do traktowania pojedynczego wystąpienia twojej struktury jako jednej konceptualnej „wartości”, niepodzielnej i odmiennej od (ale być może równej) wszystkich innych. Jeśli wykonają operację na „wartości” zapisanej przez Twój typ, otrzymają nową „wartość”, która różni się od ich wartości początkowej,
Dla dobrego przykładu spójrz na typ DateTime. Nie można bezpośrednio przypisać żadnego z pól instancji DateTime; musisz albo utworzyć nową, albo wywołać metodę na istniejącej, która utworzy nową instancję. Wynika to z faktu, że data i godzina są „wartością”, podobnie jak liczba 5, a zmiana liczby 5 powoduje powstanie nowej wartości, która nie jest 5. Tylko dlatego, że 5 + 1 = 6 nie oznacza, że 5 to teraz 6 ponieważ dodałeś do niego 1. DateTimes działają w ten sam sposób; 12:00 nie „staje się” 12:01, jeśli dodasz minutę, zamiast tego otrzymasz nową wartość 12:01, która różni się od 12:00. Jeśli jest to logiczny stan rzeczy dla twojego typu (dobre przykłady pojęciowe, które nie są wbudowane w .NET to Pieniądze, Dystans, Waga i inne ilości UOM, w których operacje muszą uwzględniać wszystkie części wartości), następnie użyj struktury i zaprojektuj go odpowiednio. W większości innych przypadków, w których podelementy obiektu powinny być niezależnie zmienne, użyj klasy.
źródło
Odpowiedź: „Użyj struktury dla czystych konstrukcji danych i klasy dla obiektów z operacjami” jest zdecydowanie niepoprawna IMO. Jeśli struct posiada dużą liczbę właściwości, klasa jest prawie zawsze bardziej odpowiednia. Microsoft często mówi, z punktu widzenia wydajności, jeśli Twój typ jest większy niż 16 bajtów, powinien być klasą.
https://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes
źródło
Najważniejszą różnicą między a
class
a jeststruct
to, co dzieje się w następującej sytuacji:Jaki powinien być wpływ ostatniego stwierdzenia
thing1.somePropertyOrField
? JeśliThing
struct isomePropertyOrField
jest odsłoniętym polem publicznym, obiektything1
ithing2
zostaną „oderwane” od siebie, więc to ostatnie zdanie nie będzie miało wpływuthing1
. JeśliThing
jest klasą, wtedything1
ithing2
będą do siebie przywiązane, a więc ta ostatnia instrukcja zapiszething1.somePropertyOrField
. Należy użyć struktury w przypadkach, w których poprzednia semantyka miałaby większy sens, i należy użyć klasy w przypadkach, w których druga semantyka miałaby większy sens.Zauważ, że chociaż niektórzy ludzie radzą, że chęć zrobienia czegoś zmiennego jest argumentem przemawiającym za tym, że jest to klasa, sugerowałbym, że jest odwrotnie: jeśli coś, co istnieje w celu przechowywania niektórych danych, będzie podlegać zmianom, i jeśli nie będzie oczywiste, czy instancje są dołączone do czegokolwiek, rzecz powinna być strukturą (prawdopodobnie z odsłoniętymi polami), aby wyjaśnić, że instancje nie są dołączone do niczego innego.
Rozważmy na przykład stwierdzenia:
Czy drugie zdanie zmieni informacje w nim przechowywane
myPeople
? JeśliPerson
jest strukturą pola narażonego, nie będzie, a fakt, że nie będzie oczywistą konsekwencją bycia strukturą pola narażonego ; jeśliPerson
jest strukturą i chce się zaktualizowaćmyPeople
, to oczywiście trzeba coś zrobićmyPeople.UpdatePerson("123-45-6789", somePerson)
. JeśliPerson
jest to klasa, może być znacznie trudniej ustalić, czy powyższy kod nigdy nie zaktualizuje zawartościMyPeople
, zawsze ją zaktualizuje, a czasem zaktualizuje.Jeśli chodzi o pogląd, że struktury powinny być „niezmienne”, ogólnie nie zgadzam się. Istnieją uzasadnione przypadki użycia „niezmiennych” struktur (gdzie niezmienniki są wymuszane w konstruktorze), ale wymaganie, aby cała struktura musiała zostać przepisana za każdym razem, gdy jakakolwiek część jej zmian jest niewygodna, marnotrawna i bardziej skłonna do powodowania błędów niż po prostu ujawnianie pola bezpośrednio. Na przykład rozważmy
PhoneNumber
strukturę, której pola obejmują między innymi,AreaCode
iExchange
, i załóżmy, że maList<PhoneNumber>
. Następujący efekt powinien być dość wyraźny:Zauważ, że nic w powyższym kodzie nie wie ani nie troszczy się o żadne pola
PhoneNumber
inne niżAreaCode
iExchange
. GdybyPhoneNumber
był tak zwaną „niezmienną” strukturą, konieczne byłoby albo dostarczeniewithXX
metody dla każdego pola, która zwróciłaby nową instancję struktury, która posiadałaby przekazaną wartość we wskazanym polu, albo byłaby konieczna dla kodu takiego jak wyżej, aby wiedzieć o każdym polu w strukturze. Niezupełnie atrakcyjne.BTW, istnieją co najmniej dwa przypadki, w których struktury mogą zasadnie zawierać odniesienia do typów zmiennych.
W poprzednim scenariuszu TOŻSAMOŚĆ obiektu będzie niezmienna; w drugim przypadku klasa zagnieżdżonego obiektu może nie wymuszać niezmienności, ale struktura przechowująca odwołanie to zrobi.
źródło
Zwykle używam struktur tylko wtedy, gdy potrzebna jest jedna metoda, przez co klasa wydaje się zbyt „ciężka”. Dlatego czasami traktuję konstrukcje jako lekkie przedmioty. UWAGA: to nie zawsze działa i nie zawsze jest najlepszą rzeczą, więc naprawdę zależy od sytuacji!
DODATKOWE ZASOBY
Aby uzyskać bardziej formalne wyjaśnienia, MSDN mówi: http://msdn.microsoft.com/en-us/library/ms229017.aspx Ten link weryfikuje to, o czym wspomniałem powyżej, struktury powinny być używane tylko do przechowywania niewielkiej ilości danych.
źródło
Użyj struktury dla czystych konstrukcji danych i klasy dla obiektów z operacjami.
Jeśli struktura danych nie wymaga kontroli dostępu i nie ma żadnych specjalnych operacji innych niż get / set, użyj struktury. To sprawia, że oczywiste jest, że cała ta struktura jest pojemnikiem na dane.
Jeśli twoja struktura ma operacje, które modyfikują strukturę w bardziej złożony sposób, użyj klasy. Klasa może także wchodzić w interakcje z innymi klasami poprzez argumenty do metod.
Pomyśl o tym jak o różnicy między cegłą a samolotem. Cegła jest strukturą; ma długość, szerokość i wysokość. Nie może wiele zdziałać. Samolot może mieć wiele operacji, które go w jakiś sposób mutują, takie jak poruszanie powierzchniami sterującymi i zmiana ciągu silnika. Byłoby lepiej jako klasa.
źródło