Jaka jest różnica między strukturą a klasą w .NET?


Odpowiedzi:


1058

W .NET istnieją dwie kategorie typów, typy referencyjne i typy wartości .

Struktury są typami wartości, a klasy są typami referencyjnymi .

Ogólna różnica polega na tym, że typ odniesienia znajduje się na stercie, a typ wartości - w wierszu, to znaczy tam, gdzie jest zdefiniowana zmienna lub pole.

Zmienna zawierająca typ wartości zawiera całą wartość typu wartości. W przypadku struct oznacza to, że zmienna zawiera całą strukturę wraz ze wszystkimi jej polami.

Zmienna zawierająca typ odwołania zawiera wskaźnik lub odwołanie do innego miejsca w pamięci, w którym znajduje się rzeczywista wartość.

Ma to jedną zaletę, na początek:

  • typy wartości zawsze zawierają wartość
  • typy referencyjne mogą zawierać wartości null -reference, co oznacza, że nie odnoszą się one do niczego w ogóle w tej chwili

Wewnętrznie typy referencyjne są implementowane jako wskaźniki, a wiedząc, że i wiedząc, jak działa przypisywanie zmiennych, istnieją inne wzorce zachowań:

  • kopiując zawartość zmiennej typu wartości do innej zmiennej, kopiuje całą zawartość do nowej zmiennej, rozróżniając obie. Innymi słowy, po skopiowaniu zmiany w jednym nie wpłyną na drugie
  • kopiowanie zawartości zmiennej typu odwołania do innej zmiennej powoduje skopiowanie odwołania, co oznacza, że ​​masz teraz dwa odniesienia do tego samego, gdzieś indziej, przechowywania rzeczywistych danych. Innymi słowy, po skopiowaniu zmiana danych w jednym odnośniku będzie również miała wpływ na drugi, ale tylko dlatego, że tak naprawdę patrzysz na te same dane w obu miejscach

Kiedy deklarujesz zmienne lub pola, oto jak różnią się dwa typy:

  • zmienna: typ wartości żyje na stosie, typ referencyjny żyje na stosie jako wskaźnik do miejsca w pamięci stosu, w którym żyje rzeczywista pamięć (choć należy zauważyć, że seria artykułów Erica Lippertsa: Stos jest szczegółem implementacji ).
  • class / struct-field: typ wartości żyje całkowicie wewnątrz typu, typ referencyjny mieszka wewnątrz typu jako wskaźnik do miejsca w pamięci sterty, w którym znajduje się rzeczywista pamięć.

43
W trosce o pełną kompletność powinienem wspomnieć, że Eric Lippert powiedział, że stos jest szczegółem implementacji , ilekroć wspomnę o stosie powyżej, miej na uwadze posty Erica.
Lasse V. Karlsen

2
Czy to wszystko dotyczy również C ++?
Koray Tugay

9
inną istotną różnicą jest użycie. Z MSDN: „struktury są zwykle używane do enkapsulacji małej grupy powiązanych zmiennych, takich jak współrzędne prostokąta. Struktury mogą również zawierać konstruktory, stałe, pola, metody, właściwości, indeksatory, operatory, zdarzenia i typy zagnieżdżone, chociaż kilka takich członkowie są wymagani, zamiast tego powinieneś rozważyć uczynienie swojego typu klasą. ”
thewpfguy

4
@KorayTugay Nie, nie jest.
ZoomIn

9
@KorayTugay w C ++ struct i klasie są absolutnie równoważne, z wyjątkiem jednej rzeczy - domyślne ograniczenie dostępu (klasa ma domyślnie private, struct ma public)
berkus

207

Krótkie podsumowanie każdego:

Tylko klasy:

  • Może wspierać dziedziczenie
  • Są typami odniesienia (wskaźnikami)
  • Odwołanie może być zerowe
  • Obciążenie pamięci dla każdej nowej instancji

Tylko konstrukcje:

  • Nie można obsłużyć dziedziczenia
  • Są typami wartości
  • Są przekazywane przez wartość (jak liczby całkowite)
  • Nie może mieć wartości zerowej (chyba że użyto wartości zerowej)
  • Nie ma narzutu pamięci dla nowej instancji - chyba że jest „zapakowany”

Zarówno klasy, jak i struktury:

  • Są złożone typy danych zwykle używane do przechowywania kilku zmiennych, które mają pewne logiczne powiązania
  • Może zawierać metody i zdarzenia
  • Może obsługiwać interfejsy

16
Niektóre części tej odpowiedzi nie są do końca właściwe. Klasy nie zawsze idą na stos, a struktury nie zawsze idą na stos. Aktualne wyjątki obejmują pola struktury w klasie, przechwycone zmienne w metodach anonimowych i wyrażenia lambda, bloki iteratora oraz wspomniane już wartości w ramkach. Ale alokacja stosu vs sterty jest szczegółem implementacji i może ulec zmianie. Eric lippart omawia to tutaj . Zgłosiłem się negatywnie, ale chętnie go usunę, jeśli zaktualizujesz.
Simon P. Stevens,

1
struct nie obsługuje dziedziczenia z innych struktur / klas, ale MOŻNA implementować interfejs na struct.
thewpfguy

2
Możesz wyjaśnić, co masz na myśli, gdy twierdzisz, że struktura „Nie ma narzutu pamięci na nową instancję” . Moja pierwsza interpretacja była taka, że ​​twierdzisz - oczywiście absurdalnie - że struktury używają zerowej pamięci. Pomyślałem wtedy, że może próbujesz powiedzieć, że struct, w przeciwieństwie do klasy, wymaga dokładnie tyle samo pamięci, co suma pól członków, i nic więcej. Ale potem poszukałem Google'a c# struct memory overheadi znalazłem odpowiedź Hansa Passanta, która mówi, że nie, to też nie jest przypadek. Więc co ma pan na myśli?
Mark Amery

4
@MarkAmery Miałem taką samą początkową reakcję jak ty na wyrażenie „brak narzutu pamięci”, ale myślę, że OP odnosi się do faktu, że instancjami classzarządzana jest pamięć (obsługiwana przez moduł odśmiecający), podczas gdy instancje structnie są .
Hutch,

1
„Struct są przekazywane przez wartość (jak liczby całkowite)” jest fałszem: wszystkie zmienne są przekazywane przez wartość, również typ odniesienia. Jeśli chcesz przekazać zmienną przez referencję, musisz użyć słowa kluczowego „ref”. jonskeet.uk/csharp/parameters.html#ref
Marco Staffoli

41

W .NET deklaracje struct i class rozróżniają typy referencyjne i typy wartości.

Kiedy omijasz typ referencyjny, jest tylko jeden faktycznie zapisany. Cały kod, który uzyskuje dostęp do instancji, ma dostęp do tego samego.

Gdy podasz typ wartości, każdy z nich jest kopią. Cały kod działa na własnej kopii.

Można to pokazać na przykładzie:

struct MyStruct 
{
    string MyProperty { get; set; }
}

void ChangeMyStruct(MyStruct input) 
{ 
   input.MyProperty = "new value";
}

...

// Create value type
MyStruct testStruct = new MyStruct { MyProperty = "initial value" }; 

ChangeMyStruct(testStruct);

// Value of testStruct.MyProperty is still "initial value"
// - the method changed a new copy of the structure.

Dla klasy byłoby inaczej

class MyClass 
{
    string MyProperty { get; set; }
}

void ChangeMyClass(MyClass input) 
{ 
   input.MyProperty = "new value";
}

...

// Create reference type
MyClass testClass = new MyClass { MyProperty = "initial value" };

ChangeMyClass(testClass);

// Value of testClass.MyProperty is now "new value" 
// - the method changed the instance passed.

Klasy mogą być niczym - odwołanie może wskazywać na zero.

Struktury są rzeczywistą wartością - mogą być puste, ale nigdy zerowe. Z tego powodu struktury zawsze mają domyślny konstruktor bez parametrów - potrzebują „wartości początkowej”.


@ T.Todua tak, są lepsze odpowiedzi powyżej, które zagłosowałem i wybrałem jako odpowiedź po dostarczeniu tego - pochodzi z wczesnej wersji beta SO, kiedy wciąż opracowywaliśmy zasady.
Keith

1
Nie wiem, czy dobrze mnie zrozumiałeś, naprawdę głosowałem / zaakceptowałem twoją odpowiedź (w przeciwieństwie do powyższych odpowiedzi), ponieważ miałeś dobre przykłady (nie tylko wyjaśnienia teoretyczne, w przeciwieństwie do powyższej odpowiedzi, które miały tylko wyjaśnienia teoretyczne bez przykładów ).
T.Todua

24

Różnica między strukturami i klasami:

  • Struktury są typem wartości, natomiast Klasy są typem referencyjnym .
  • Struktury są przechowywane na stosie, natomiast klasy są przechowywane na stercie .
  • Typy wartości przechowują swoją wartość w pamięci w miejscu, w którym zostały zadeklarowane, ale typ odwołania zawiera odwołanie do pamięci obiektu.
  • Typy wartości niszczone natychmiast po utracie zakresu, natomiast typ odniesienia tylko zmienna niszcza po utracie zakresu. Obiekt jest później niszczony przez śmietnik.
  • Kiedy kopiujesz struct do innej struktury, tworzona jest nowa kopia tej struktury modyfikowana dla jednej struktury nie wpłynie na wartość drugiej struktury.
  • Kiedy kopiujesz klasę do innej klasy, kopiuje ona tylko zmienną referencyjną.
  • Obie zmienne referencyjne wskazują ten sam obiekt na stercie. Zmiana na jedną zmienną wpłynie na drugą zmienną odniesienia.
  • Struktury nie mogą mieć destruktorów , ale klasy mogą mieć destruktory.
  • Struktury nie mogą mieć jawnych konstruktorów bez parametrów, podczas gdy klasa może struktury nie obsługuje dziedziczenia, ale klasy tak. Oba obsługują dziedziczenie z interfejsu.
  • Konstrukcje są uszczelnione .

21

Od wyboru między klasą a strukturą Microsoftu ...

Z reguły większość typów w ramach powinna stanowić klasy. Istnieją jednak pewne sytuacje, w których właściwości typu wartości sprawiają, że bardziej odpowiednie jest użycie struktur.

UWAŻAJ strukturę zamiast klasy:

  • Jeśli wystąpienia tego typu są małe i zwykle krótkotrwałe lub są zwykle osadzone w innych obiektach.

X UNIKAĆ struktury, chyba że typ ma wszystkie następujące cechy:

  • Logicznie reprezentuje pojedynczą wartość, podobną do typów pierwotnych (int, double itp.).
  • Ma rozmiar instancji poniżej 16 bajtów.
  • Jest niezmienny. (nie może zostać zmieniony)
  • Nie będzie trzeba go często zapakować.

19

Oprócz wszystkich różnic opisanych w innych odpowiedziach:

  1. Struktury nie mogą mieć jawnego konstruktora bez parametrów, podczas gdy klasa może
  2. Struktury nie mogą mieć destruktorów , podczas gdy klasa może
  3. Struktury nie mogą dziedziczyć z innej struktury lub klasy, podczas gdy klasa może dziedziczyć z innej klasy. (Zarówno struktury, jak i klasy mogą być implementowane z interfejsu.)

Jeśli szukasz filmu wyjaśniającego wszystkie różnice, możesz zapoznać się z częścią 29 - Kurs C # - Różnica między klasami a strukturami w języku C # .


4
Znacznie bardziej znaczący niż fakt, że języki .net na ogół nie pozwolą strukturowi na zdefiniowanie konstruktora bez parametrów (decyzja o tym, czy zezwolić na to jest podejmowana przez kompilator języka) jest fakt, że struktury mogą powstać i zostać ujawnione do świata zewnętrznego bez uruchamiania jakiegokolwiek konstruktora (nawet jeśli zdefiniowano konstruktor bez parametrów). Powodem, dla którego języki .net ogólnie zabraniają konstruktorów bez parametrów dla struktur, jest unikanie zamieszania, które wynikałoby z tego, że takie konstruktory byłyby czasem uruchamiane, a czasem nie.
supercat

15

Wystąpienia klas są przechowywane na zarządzanej stercie. Wszystkie zmienne „zawierające” instancję są po prostu odwołaniem do instancji na stercie. Przekazanie obiektu do metody powoduje przekazanie kopii odwołania, a nie samego obiektu.

Struktury (technicznie, typy wartości) są przechowywane wszędzie tam, gdzie są używane, podobnie jak typ pierwotny. Zawartość może być kopiowana przez środowisko wykonawcze w dowolnym momencie i bez wywoływania dostosowanego konstruktora kopii. Przekazanie typu wartości do metody wymaga skopiowania całej wartości, ponownie bez wywoływania dostosowywanego kodu.

Rozróżnienie jest poprawione przez nazwy C ++ / CLI: „klasa referencyjna” jest klasą, jak opisano najpierw, „klasa wartości” jest klasą opisaną jako druga. Słowa kluczowe „class” i „struct” używane w języku C # są po prostu czymś, czego należy się nauczyć.


11
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
|                        |                                                Struct                                                |                                               Class                                               |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| Type                   | Value-type                                                                                           | Reference-type                                                                                    |
| Where                  | On stack / Inline in containing type                                                                 | On Heap                                                                                           |
| Deallocation           | Stack unwinds / containing type gets deallocated                                                     | Garbage Collected                                                                                 |
| Arrays                 | Inline, elements are the actual instances of the value type                                          | Out of line, elements are just references to instances of the reference type residing on the heap |
| Aldel Cost             | Cheap allocation-deallocation                                                                        | Expensive allocation-deallocation                                                                 |
| Memory usage           | Boxed when cast to a reference type or one of the interfaces they implement,                         | No boxing-unboxing                                                                                |
|                        | Unboxed when cast back to value type                                                                 |                                                                                                   |
|                        | (Negative impact because boxes are objects that are allocated on the heap and are garbage-collected) |                                                                                                   |
| Assignments            | Copy entire data                                                                                     | Copy the reference                                                                                |
| Change to an instance  | Does not affect any of its copies                                                                    | Affect all references pointing to the instance                                                    |
| Mutability             | Should be immutable                                                                                  | Mutable                                                                                           |
| Population             | In some situations                                                                                   | Majority of types in a framework should be classes                                                |
| Lifetime               | Short-lived                                                                                          | Long-lived                                                                                        |
| Destructor             | Cannot have                                                                                          | Can have                                                                                          |
| Inheritance            | Only from an interface                                                                               | Full support                                                                                      |
| Polymorphism           | No                                                                                                   | Yes                                                                                               |
| Sealed                 | Yes                                                                                                  | When have sealed keyword                                                                          |
| Constructor            | Can not have explicit parameterless constructors                                                     | Any constructor                                                                                   |
| Null-assignments       | When marked with nullable question mark                                                              | Yes (+ When marked with nullable question mark in C# 8+)                                          |
| Abstract               | No                                                                                                   | When have abstract keyword                                                                        |
| Member Access Modifiers| public, private, internal                                                                            | public, protected, internal, protected internal, private protected                                |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+

1
To jest naprawdę wspaniałe: podsumowane i pouczające. Pamiętaj tylko, aby przynajmniej raz przeczytać korektę odpowiedzi - zamieniłeś wyjaśnienia dotyczące struktury i klas w niektórych wierszach, a także literówki.
Robert Synoradzki

1
@ensisNoctis Przepraszamy za błędy i dziękuję za edycję. Powinienem ponownie przeczytać moje odpowiedzi 😅
0xaryan

8

Struktura a klasa

Struktura jest typem wartości, więc jest przechowywana na stosie, ale klasa jest typem referencyjnym i jest przechowywana na stercie.

Struktura nie obsługuje dziedziczenia i polimorfizmu, ale klasa obsługuje oba te elementy.

Domyślnie wszyscy członkowie struktury są publiczni, ale członkowie klasy mają z natury charakter prywatny.

Ponieważ struktura jest typem wartości, nie możemy przypisać wartości null do obiektu struct, ale nie jest tak w przypadku klasy.


5
Jeśli chodzi o „wszyscy członkowie struktury są publiczni”: jeśli się nie mylę, jest to nieprawda. „Poziom dostępu dla członków klas i struktur, w tym klas zagnieżdżonych i struktur, jest domyślnie prywatny.” msdn.microsoft.com/en-us/library/ms173121.aspx
Nate Cook

8

Aby dodać do innych odpowiedzi, należy zwrócić uwagę na jedną zasadniczą różnicę, a mianowicie sposób przechowywania danych w tablicach, ponieważ może to mieć duży wpływ na wydajność.

  • W przypadku struct tablica zawiera instancję struct
  • W przypadku klasy tablica zawiera wskaźnik do wystąpienia klasy w innym miejscu w pamięci

Tak więc tablica struktur wygląda tak w pamięci

[struct][struct][struct][struct][struct][struct][struct][struct]

Natomiast tablica klas wygląda tak

[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]

W tablicy klas wartości, które Cię interesują, nie są przechowywane w tablicy, ale w innej pamięci.

W zdecydowanej większości aplikacji różnica ta nie ma tak naprawdę znaczenia, jednak w kodzie o wysokiej wydajności wpłynie to na lokalizację danych w pamięci i będzie miało duży wpływ na wydajność pamięci podręcznej procesora. Używanie klas, gdy można / należy użyć struktur, znacznie zwiększy liczbę braków pamięci podręcznej na procesorze.

Najwolniejszą rzeczą, jaką robi współczesny procesor, nie jest niszczenie liczb, pobieranie danych z pamięci, a trafienie w pamięć podręczną L1 jest wielokrotnie szybsze niż odczyt danych z pamięci RAM.

Oto kod, który możesz przetestować. Na moim komputerze iteracja po tablicy klas zajmuje ~ 3 razy dłużej niż tablica struct.

    private struct PerformanceStruct
    {
        public int i1;
        public int i2;
    }

    private class PerformanceClass
    {
        public int i1;
        public int i2;
    }

    private static void DoTest()
    {
        var structArray = new PerformanceStruct[100000000];
        var classArray = new PerformanceClass[structArray.Length];

        for (var i = 0; i < structArray.Length; i++)
        {
            structArray[i] = new PerformanceStruct();
            classArray[i] = new PerformanceClass();
        }

        long total = 0;
        var sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < structArray.Length; i++)
        {
            total += structArray[i].i1 + structArray[i].i2;
        }

        sw.Stop();
        Console.WriteLine($"Struct Time: {sw.ElapsedMilliseconds}");
        sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < classArray.Length; i++)
        {
            total += classArray[i].i1 + classArray[i].i2;
        }

        Console.WriteLine($"Class Time: {sw.ElapsedMilliseconds}");
    }

-1; „Struktury są typami wartości, więc przechowują wartość, klasy są typami referencyjnymi, więc odwołują się do klasy”. jest niejasne i mało prawdopodobne, aby miało to sens dla każdego, kto jeszcze nie zrozumiał tego na podstawie innych odpowiedzi tutaj, i „W przypadku klasy zawierająca klasa będzie zawierała wskaźnik do nowej klasy w innym obszarze pamięci”. myli klasy z instancjami klasy.
Mark Amery

@ MarkAmery Próbowałem nieco wyjaśnić. Najważniejszą rzeczą, na którą naprawdę próbowałem zwrócić uwagę, była różnica w sposobie, w jaki tablice działają z typami wartości i referencji, oraz wpływ, jaki to ma na wydajność. Nie próbowałem ponownie wyjaśniać, jakie są wartości i typy referencji, ponieważ dzieje się tak w wielu innych odpowiedziach.
Will Calderwood,

7

Aby go ukończyć, istnieje inna różnica w korzystaniu z Equalsmetody, która jest dziedziczona przez wszystkie klasy i struktury.

Powiedzmy, że mamy klasę i strukturę:

class A{
  public int a, b;
}
struct B{
  public int a, b;
}

a w metodzie Main mamy 4 obiekty.

static void Main{
  A c1 = new A(), c2 = new A();
  c1.a = c1.b = c2.a = c2.b = 1;
  B s1 = new B(), s2 = new B();
  s1.a = s1.b = s2.a = s2.b = 1;
}

Następnie:

s1.Equals(s2) // true
s1.Equals(c1) // false
c1.Equals(c2) // false
c1 == c2 // false

Tak więc struktury są odpowiednie dla obiektów numerycznych, takich jak punkty (zapisz współrzędne xiy). A zajęcia są odpowiednie dla innych. Nawet jeśli 2 osoby mają to samo imię, wzrost, wagę ... to nadal są 2 osoby.


6

Cóż, na początek, struktura jest przekazywana raczej przez wartość niż przez referencję. Struktury są dobre dla stosunkowo prostych struktur danych, podczas gdy klasy mają znacznie większą elastyczność z architektonicznego punktu widzenia dzięki polimorfizmowi i dziedziczeniu.

Inni prawdopodobnie zapewnią ci więcej szczegółów niż ja, ale używam struktur, gdy struktura, do której dążę, jest prosta.


4

Oprócz podstawowej różnicy między specyfikatorem dostępu i kilkoma wymienionymi powyżej, chciałbym dodać niektóre z głównych różnic, w tym kilka wspomnianych powyżej, z próbką kodu z wyjściem, która da wyraźniejszy obraz odniesienia i wartości

Struktury:

  • Są typami wartości i nie wymagają alokacji sterty.
  • Alokacja pamięci jest inna i jest przechowywana w stosie
  • Przydatny w przypadku małych struktur danych
  • Wpływa na wydajność, gdy przekazujemy wartość do metody, przekazujemy całą strukturę danych i wszystko jest przekazywane do stosu.
  • Konstruktor po prostu zwraca samą wartość struktury (zazwyczaj w tymczasowej lokalizacji na stosie), a następnie ta wartość jest kopiowana w razie potrzeby
  • Każda ze zmiennych ma własną kopię danych i operacje na jednym z nich nie mają wpływu na drugi.
  • Nie obsługują dziedziczenia określonego przez użytkownika i domyślnie dziedziczą po obiekcie typu

Klasa:

  • Wartość typu odniesienia
  • Przechowywane w kupie
  • Przechowuj odniesienie do dynamicznie przydzielanego obiektu
  • Konstruktory są wywoływane z nowym operatorem, ale to nie przydziela pamięci na stercie
  • Wiele zmiennych może mieć odwołanie do tego samego obiektu
  • Możliwe jest, że operacje na jednej zmiennej wpływają na obiekt, do którego odwołuje się druga zmienna

Próbka kodu

    static void Main(string[] args)
    {
        //Struct
        myStruct objStruct = new myStruct();
        objStruct.x = 10;
        Console.WriteLine("Initial value of Struct Object is: " + objStruct.x);
        Console.WriteLine();
        methodStruct(objStruct);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Struct Object is: " + objStruct.x);
        Console.WriteLine();

        //Class
        myClass objClass = new myClass(10);
        Console.WriteLine("Initial value of Class Object is: " + objClass.x);
        Console.WriteLine();
        methodClass(objClass);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Class Object is: " + objClass.x);
        Console.Read();
    }
    static void methodStruct(myStruct newStruct)
    {
        newStruct.x = 20;
        Console.WriteLine("Inside Struct Method");
        Console.WriteLine("Inside Method value of Struct Object is: " + newStruct.x);
    }
    static void methodClass(myClass newClass)
    {
        newClass.x = 20;
        Console.WriteLine("Inside Class Method");
        Console.WriteLine("Inside Method value of Class Object is: " + newClass.x);
    }
    public struct myStruct
    {
        public int x;
        public myStruct(int xCons)
        {
            this.x = xCons;
        }
    }
    public class myClass
    {
        public int x;
        public myClass(int xCons)
        {
            this.x = xCons;
        }
    }

Wynik

Początkowa wartość obiektu konstrukcyjnego to: 10

Metoda konstrukcji wewnętrznej Wartość metody wewnętrznej obiektu konstrukcyjnego wynosi: 20

Po wywołaniu metody wartość obiektu konstrukcyjnego wynosi: 10

Początkowa wartość obiektu klasy to: 10

Metoda klasy wewnętrznej Wartość metody wewnętrznej obiektu klasy wynosi: 20

Po wywołaniu metody wartość obiektu klasy wynosi: 20

Tutaj możesz wyraźnie zobaczyć różnicę między połączeniem według wartości a wywołaniem przez odniesienie.


4
  1. Zdarzenia zadeklarowane w klasie mają swój dostęp + = i - = automatycznie blokowane przez blokadę (this), aby były bezpieczne dla wątków (zdarzenia statyczne są blokowane na typie klasy). Zdarzenia zadeklarowane w strukturze nie mają automatycznie blokowanego dostępu + = i - =. Blokada (this) dla struktury nie działałaby, ponieważ można zablokować tylko wyrażenie typu referencyjnego.

  2. Utworzenie instancji struct nie może spowodować wyrzucania elementów bezużytecznych (chyba że konstruktor bezpośrednio lub pośrednio utworzy instancję typu odwołania), natomiast utworzenie instancji typu odniesienia może spowodować odśmiecanie.

  3. Struktura zawsze ma wbudowany publiczny konstruktor domyślny.

    class DefaultConstructor
    {
        static void Eg()
        {
            Direct     yes = new   Direct(); // Always compiles OK
            InDirect maybe = new InDirect(); // Compiles if constructor exists and is accessible
            //...
        }
    }

    Oznacza to, że struktura jest zawsze możliwa do utworzenia, podczas gdy klasa może nie być, ponieważ wszystkie jej konstruktory mogą być prywatne.

    class NonInstantiable
    {
        private NonInstantiable() // OK
        {
        }
    }
    
    struct Direct
    {
        private Direct() // Compile-time error
        {
        }
    }
  4. Struktura nie może mieć destruktora. Destruktor to po prostu przesłonięcie obiektu. Finalizacja w przebraniu, a struktury, będące typami wartości, nie podlegają odśmiecaniu.

    struct Direct
    {
        ~Direct() {} // Compile-time error
    }
    class InDirect
    {
        ~InDirect() {} // Compiles OK
    }
    
    And the CIL for ~Indirect() looks like this:
    
    .method family hidebysig virtual instance void
            Finalize() cil managed
    {
      // ...
    } // end of method Indirect::Finalize
  5. Struktura jest domyślnie zapieczętowana, klasa nie.
    Struktura nie może być abstrakcyjna, klasa może.
    Struktur nie może wywołać: base () w swoim konstruktorze, podczas gdy klasa bez wyraźnej klasy bazowej może.
    Struktura nie może rozszerzyć innej klasy, klasa może.
    Struktura nie może deklarować chronionych elementów (na przykład pól, typów zagnieżdżonych), które klasa może.
    Struktura nie może deklarować elementów funkcji abstrakcyjnych, klasa abstrakcyjna może.
    Struktura nie może deklarować elementów funkcji wirtualnej, klasa może.
    Struktura nie może deklarować zapieczętowanych elementów funkcji, klasa może.
    Struktura nie może zadeklarować elementów funkcji zastępowania, klasa może to zrobić.
    Jedynym wyjątkiem od tej reguły jest to, że struct może zastąpić wirtualne metody System.Object, viz, Equals () i GetHashCode () oraz ToString ().


W jakich okolicznościach można użyć zdarzenia ze strukturą? Mogę sobie wyobrazić, że bardzo starannie napisany program mógłby wykorzystywać zdarzenia ze strukturą w sposób, który działałby, ale tylko wtedy, gdy struktura nigdy nie byłaby kopiowana ani przekazywana przez wartość, w którym to przypadku równie dobrze mogłaby to być klasa.
supercat,

@ superupat Tak, niestatyczne zdarzenie w strukturze byłoby bardzo dziwne i przydałoby się tylko w przypadku struktur modyfikowalnych, a samo zdarzenie (jeśli jest zdarzeniem podobnym do pola) zamienia strukturę w „zmienną” ”, a także wprowadza typ struktury odniesienia w strukturze. Niestatyczne zdarzenia w strukturach muszą być złe.
Jeppe Stig Nielsen

@JeppeStigNielsen: Jedynym wzorcem, jaki widziałem w przypadku struktury, w której zdarzenie ma sens, byłoby, gdyby celem struktury było przechowywanie niezmiennego odwołania do obiektu klasy, dla którego zachowywał się jak serwer proxy. Jednak w takim scenariuszu zdarzenia automatyczne byłyby całkowicie bezużyteczne; zamiast tego zdarzenia subskrypcji i anulowania subskrypcji musiałyby zostać przekazane klasie znajdującej się za strukturą. Chciałbym, żeby .NET miał (lub pozwoliłby na zdefiniowanie) typu struktury „cache-box” z początkowo zerowym ukrytym polem typu Object, które zawierałoby odniesienie do pudełkowej kopii struktury.
supercat

1
@JeppeStigNielsen: Struktury przewyższają klasy w wielu scenariuszach użycia proxy; największym problemem z użyciem struktur jest to, że w przypadkach, gdy boksowanie jest konieczne, często kończy się odroczeniem do wewnętrznej pętli. Gdyby istniał sposób na uniknięcie wielokrotnego pakowania struktur , byłyby one lepsze niż klasy w wielu innych scenariuszach użycia.
supercat

4

Jak wspomniano wcześniej: klasy są typem odniesienia, natomiast struktury są typami wartości ze wszystkimi konsekwencjami.

Zasadniczo Wytyczne projektowania ramowego zalecają stosowanie Struktur zamiast klas, jeśli:

  • Ma rozmiar instancji poniżej 16 bajtów
  • Logicznie reprezentuje pojedynczą wartość, podobną do typów pierwotnych (int, double itp.)
  • Jest niezmienny
  • Nie będzie trzeba go często zapakować

3

Jest jeden interesujący przypadek układanki „klasa kontra struktura” - sytuacja, w której musisz zwrócić kilka wyników z metody: wybierz, której użyć. Jeśli znasz historię ValueTuple - wiesz, że dodano ValueTuple (struct), ponieważ powinna być bardziej skuteczna niż Tuple (klasa). Ale co to oznacza w liczbach? Dwa testy: jeden to struct / klasa, która ma 2 pola, drugi z struct / class, które mają 8 pól (o wymiarze większym niż 4 - klasa powinna stać się bardziej efektywna niż struct pod względem tików procesora, ale oczywiście należy również wziąć pod uwagę obciążenie GC ).

PS Kolejnym punktem odniesienia dla konkretnego przypadku „solidna lub klasa ze zbiorami” jest: https://stackoverflow.com/a/45276657/506147

BenchmarkDotNet=v0.10.10, OS=Windows 10 Redstone 2 [1703, Creators Update] (10.0.15063.726)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233540 Hz, Resolution=309.2586 ns, Timer=TSC
.NET Core SDK=2.0.3
  [Host] : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
  Clr    : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2115.0
  Core   : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT


            Method |  Job | Runtime |     Mean |     Error |    StdDev |      Min |      Max |   Median | Rank |  Gen 0 | Allocated |
------------------ |----- |-------- |---------:|----------:|----------:|---------:|---------:|---------:|-----:|-------:|----------:|
  TestStructReturn |  Clr |     Clr | 17.57 ns | 0.1960 ns | 0.1834 ns | 17.25 ns | 17.89 ns | 17.55 ns |    4 | 0.0127 |      40 B |
   TestClassReturn |  Clr |     Clr | 21.93 ns | 0.4554 ns | 0.5244 ns | 21.17 ns | 23.26 ns | 21.86 ns |    5 | 0.0229 |      72 B |
 TestStructReturn8 |  Clr |     Clr | 38.99 ns | 0.8302 ns | 1.4097 ns | 37.36 ns | 42.35 ns | 38.50 ns |    8 | 0.0127 |      40 B |
  TestClassReturn8 |  Clr |     Clr | 23.69 ns | 0.5373 ns | 0.6987 ns | 22.70 ns | 25.24 ns | 23.37 ns |    6 | 0.0305 |      96 B |
  TestStructReturn | Core |    Core | 12.28 ns | 0.1882 ns | 0.1760 ns | 11.92 ns | 12.57 ns | 12.30 ns |    1 | 0.0127 |      40 B |
   TestClassReturn | Core |    Core | 15.33 ns | 0.4343 ns | 0.4063 ns | 14.83 ns | 16.44 ns | 15.31 ns |    2 | 0.0229 |      72 B |
 TestStructReturn8 | Core |    Core | 34.11 ns | 0.7089 ns | 1.4954 ns | 31.52 ns | 36.81 ns | 34.03 ns |    7 | 0.0127 |      40 B |
  TestClassReturn8 | Core |    Core | 17.04 ns | 0.2299 ns | 0.2150 ns | 16.68 ns | 17.41 ns | 16.98 ns |    3 | 0.0305 |      96 B |

Test kodu:

using System;
using System.Text;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Columns;
using BenchmarkDotNet.Attributes.Exporters;
using BenchmarkDotNet.Attributes.Jobs;
using DashboardCode.Routines.Json;

namespace Benchmark
{
    //[Config(typeof(MyManualConfig))]
    [RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkStructOrClass
    {
        static TestStruct testStruct = new TestStruct();
        static TestClass testClass = new TestClass();
        static TestStruct8 testStruct8 = new TestStruct8();
        static TestClass8 testClass8 = new TestClass8();
        [Benchmark]
        public void TestStructReturn()
        {
            testStruct.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn()
        {
            testClass.TestMethod();
        }


        [Benchmark]
        public void TestStructReturn8()
        {
            testStruct8.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn8()
        {
            testClass8.TestMethod();
        }

        public class TestStruct
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestClass
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestStruct8
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }

        public class TestClass8
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }
    }
}

2

Struktury są rzeczywistą wartością - mogą być puste, ale nigdy zerowe

To prawda, należy jednak pamiętać, że od .NET 2 struktury obsługują wersję Nullable, a C # dostarcza trochę cukru syntaktycznego, aby było łatwiejsze w użyciu.

int? value = null;
value  = 1;

1
Należy pamiętać, że jest to tylko cukier składniowy o treści „Nullable <int> value = null;”
Erik van Brakel,

@ErikvanBrakel To nie tylko cukier syntaktyczny. Różne reguły boksu oznaczają, (object)(default(int?)) == nullże nie możesz zrobić z żadnym innym rodzajem wartości, ponieważ dzieje się tutaj coś więcej niż tylko cukier. Jedyny cukier jest int?dla Nullable<int>.
Jon Hanna,

-1; nie odnosi się to do pytania o różnicę między strukturami a klasami i jako taki powinien być komentarzem do odpowiedzi, na którą odpowiadasz, a nie osobną odpowiedzią. (Chociaż być może normy witryn były inne w sierpniu 2008 roku!)
Mark Amery

1

Każda zmienna lub pole pierwotnego typu wartości lub typu struktury zawiera unikalną instancję tego typu, w tym wszystkie jego pola (publiczne i prywatne). Natomiast zmienne lub pola typów odwołań mogą mieć wartość null lub mogą odnosić się do obiektu przechowywanego w innym miejscu, do którego może istnieć dowolna liczba innych odwołań. Pola struktury będą przechowywane w tym samym miejscu, co zmienna lub pole tego typu struktury, które mogą znajdować się na stosie lub mogą być częścią innego obiektu stosu.

Utworzenie zmiennej lub pola o pierwotnym typie wartości spowoduje utworzenie go z wartością domyślną; utworzenie zmiennej lub pola typu struktury spowoduje utworzenie nowej instancji, tworząc wszystkie pola w niej w sposób domyślny. Utworzenie nowej instancji typu odwołania rozpocznie się od utworzenia wszystkich pól w niej w sposób domyślny, a następnie uruchomienia opcjonalnego dodatkowego kodu w zależności od typu.

Skopiowanie jednej zmiennej lub pola typu pierwotnego do innej spowoduje skopiowanie wartości. Skopiowanie jednej zmiennej lub pola typu struktury do drugiej spowoduje skopiowanie wszystkich pól (publicznych i prywatnych) z pierwszej instancji do drugiej. Skopiowanie jednej zmiennej lub pola typu odniesienia do innego spowoduje, że ta ostatnia będzie odnosić się do tej samej instancji co pierwsza (jeśli istnieje).

Należy zauważyć, że w niektórych językach, takich jak C ++, zachowanie semantyczne typu jest niezależne od sposobu jego przechowywania, ale nie jest to prawdą w przypadku .NET. Jeśli typ implementuje semantykę wartości zmiennej, kopiowanie jednej zmiennej tego typu do innej kopiuje właściwości pierwszej do innej instancji, do której odwołuje się druga, i użycie elementu drugiej mutacji spowoduje zmianę tej drugiej instancji , ale nie pierwszy. Jeśli typ implementuje zmienną semantykę odniesienia, skopiowanie jednej zmiennej do drugiej i użycie elementu drugiej do mutacji obiektu wpłynie na obiekt, do którego odnosi się pierwsza zmienna; typy z niezmienną semantyką nie pozwalają na mutację, więc nie ma znaczenia semantycznego, czy kopiowanie tworzy nową instancję, czy tworzy kolejne odwołanie do pierwszej.

W .NET typy wartości mogą implementować dowolną z powyższych semantyek, pod warunkiem, że wszystkie ich pola mogą zrobić podobnie. Typ referencyjny może jednak implementować tylko semantykę zmienną referencyjną lub semantykę niezmienną; typy wartości z polami zmiennych typów zmiennych są ograniczone do implementacji semantyki zmiennych zmiennych lub dziwnej hybrydowej semantyki.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.