Wann sollte ich in C # eine Struktur anstelle einer Klasse verwenden?


1390

Wann sollten Sie struct und nicht class in C # verwenden? Mein konzeptionelles Modell ist, dass Strukturen in Zeiten verwendet werden, in denen das Element lediglich eine Sammlung von Werttypen ist . Eine Möglichkeit, sie alle logisch zu einem zusammenhängenden Ganzen zusammenzuhalten.

Ich bin hier auf diese Regeln gestoßen :

  • Eine Struktur sollte einen einzelnen Wert darstellen.
  • Eine Struktur sollte einen Speicherbedarf von weniger als 16 Byte haben.
  • Eine Struktur sollte nach der Erstellung nicht geändert werden.

Funktionieren diese Regeln? Was bedeutet eine Struktur semantisch?


247
System.Drawing.Rectangleverstößt gegen alle drei Regeln.
ChrisW

4
Es gibt einige kommerzielle Spiele, die in C # geschrieben sind. Der Punkt ist, dass sie für optimierten Code verwendet werden
BlackTigerX

25
Strukturen bieten eine bessere Leistung, wenn Sie über kleine Sammlungen von Werttypen verfügen, die Sie zusammenfassen möchten. Dies passiert die ganze Zeit in der Spielprogrammierung. Beispielsweise hat ein Scheitelpunkt in einem 3D-Modell eine Position, eine Texturkoordinate und eine Normalen. Außerdem ist er im Allgemeinen unveränderlich. Ein einzelnes Modell kann ein paar tausend Eckpunkte oder ein Dutzend haben, aber Strukturen bieten in diesem Verwendungsszenario insgesamt weniger Overhead. Ich habe dies durch mein eigenes Motordesign überprüft.
Chris D.


4
@ChrisW Ich sehe, aber stellen diese Werte nicht ein Rechteck dar, dh einen "einzelnen" Wert? Wie Vector3D oder Color sind auch hier mehrere Werte enthalten, aber ich denke, sie repräsentieren einzelne Werte?
Marson Mao

Antworten:


604

Die Quelle, auf die das OP verweist, hat eine gewisse Glaubwürdigkeit ... aber was ist mit Microsoft - wie steht es zur Strukturnutzung? Ich habe mich bei Microsoft um zusätzliche Informationen bemüht und Folgendes gefunden:

Ziehen Sie in Betracht, eine Struktur anstelle einer Klasse zu definieren, wenn Instanzen des Typs klein und häufig von kurzer Dauer sind oder häufig in andere Objekte eingebettet sind.

Definieren Sie keine Struktur, es sei denn, der Typ weist alle folgenden Merkmale auf:

  1. Es stellt logischerweise einen einzelnen Wert dar, ähnlich wie primitive Typen (Ganzzahl, Doppel usw.).
  2. Die Instanzgröße ist kleiner als 16 Byte.
  3. Es ist unveränderlich.
  4. Es muss nicht häufig verpackt werden.

Microsoft verstößt konsequent gegen diese Regeln

Okay, # 2 und # 3 sowieso. Unser geliebtes Wörterbuch hat 2 interne Strukturen:

[StructLayout(LayoutKind.Sequential)]  // default for structs
private struct Entry  //<Tkey, TValue>
{
    //  View code at *Reference Source
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator : 
    IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, 
    IDictionaryEnumerator, IEnumerator
{
    //  View code at *Reference Source
}

* Referenzquelle

Die Quelle 'JonnyCantCode.com' erhielt 3 von 4 Punkten - ziemlich verzeihlich, da # 4 wahrscheinlich kein Problem darstellen würde. Wenn Sie feststellen, dass Sie eine Struktur boxen, überdenken Sie Ihre Architektur.

Schauen wir uns an, warum Microsoft diese Strukturen verwenden würde:

  1. Jede Struktur Entryund Enumeratorrepräsentiert einzelne Werte.
  2. Geschwindigkeit
  3. Entrywird niemals als Parameter außerhalb der Dictionary-Klasse übergeben. Weitere Untersuchungen zeigen, dass Dictionary zur Erfüllung der Implementierung von IEnumerable die EnumeratorStruktur verwendet, die es jedes Mal kopiert, wenn ein Enumerator angefordert wird ... sinnvoll ist.
  4. Intern in der Dictionary-Klasse. Enumeratorist öffentlich, da Dictionary aufzählbar ist und den gleichen Zugriff auf die Implementierung der IEnumerator-Schnittstelle haben muss - z. B. IEnumerator getter.

Aktualisieren - Beachten Sie außerdem, dass, wenn eine Struktur - wie Enumerator - eine Schnittstelle implementiert und in diesen implementierten Typ umgewandelt wird, die Struktur zu einem Referenztyp wird und in den Heap verschoben wird. Enumerator ist in der Dictionary-Klasse immer noch ein Werttyp. Sobald jedoch eine Methode aufgerufen wird GetEnumerator(), wird ein Referenztyp IEnumeratorzurückgegeben.

Was wir hier nicht sehen, ist ein Versuch oder ein Beweis für die Anforderung, Strukturen unveränderlich zu halten oder eine Instanzgröße von nur 16 Bytes oder weniger beizubehalten:

  1. Nichts in den obigen Strukturen wird deklariert readonly- nicht unveränderlich
  2. Die Größe dieser Struktur könnte weit über 16 Bytes liegen
  3. Entryhat eine unbestimmte Lebensdauer (von Add(), auf Remove(), Clear()oder garbage collection);

Und ... 4. Beide Strukturen speichern TKey und TValue, von denen wir alle wissen, dass sie durchaus Referenztypen sein können (zusätzliche Bonusinformationen).

Ungeachtet der Hashed Keys sind Wörterbücher teilweise schnell, da das Instanziieren einer Struktur schneller ist als ein Referenztyp. Hier habe ich eine Dictionary<int, int>, die 300.000 zufällige Ganzzahlen mit sequentiell inkrementierten Schlüsseln speichert.

Kapazität: 312874
MemSize: 2660827 Bytes
Abgeschlossen Größe ändern: 5 ms Gesamtfüllzeit: 889
ms

Kapazität : Anzahl der verfügbaren Elemente, bevor die Größe des internen Arrays geändert werden muss.

MemSize : Wird ermittelt, indem das Wörterbuch in einen MemoryStream serialisiert und eine Bytelänge ermittelt wird (genau genug für unsere Zwecke).

Abgeschlossene Größenänderung : Die Zeit, die benötigt wird, um die Größe des internen Arrays von 150862 Elementen auf 312874 Elemente zu ändern. Wenn Sie sich vorstellen, dass jedes Element nacheinander kopiert Array.CopyTo()wird, ist das nicht allzu schäbig.

Gesamtzeit zum Füllen : Zugegebenermaßen aufgrund der Protokollierung und eines OnResizeEreignisses, das ich der Quelle hinzugefügt habe, verzerrt ; Es ist jedoch immer noch beeindruckend, 300.000 Ganzzahlen zu füllen, während die Größe während des Vorgangs 15 Mal geändert wird. Was wäre die Gesamtzeit aus Neugier, wenn ich die Kapazität bereits kennen würde? 13ms

Was wäre Entrynun eine Klasse? Würden sich diese Zeiten oder Metriken wirklich so stark unterscheiden?

Kapazität: 312874
Speichergröße: 2660827 Bytes
Abgeschlossen Größe ändern: 26 ms Gesamtfüllzeit: 964
ms

Offensichtlich liegt der große Unterschied in der Größenänderung. Gibt es einen Unterschied, wenn das Wörterbuch mit der Kapazität initialisiert wird? Nicht genug, um sich mit ... 12ms zu befassen .

Da Entryes sich um eine Struktur handelt, ist keine Initialisierung wie bei einem Referenztyp erforderlich. Dies ist sowohl die Schönheit als auch der Fluch des Werttyps. Um Entryals Referenztyp zu verwenden, musste ich den folgenden Code einfügen:

/*
 *  Added to satisfy initialization of entry elements --
 *  this is where the extra time is spent resizing the Entry array
 * **/
for (int i = 0 ; i < prime ; i++)
{
    destinationArray[i] = new Entry( );
}
/*  *********************************************** */  

Der Grund, warum ich jedes Array-Element Entryals Referenztyp initialisieren musste, finden Sie unter MSDN: Structure Design . Zusamenfassend:

Geben Sie keinen Standardkonstruktor für eine Struktur an.

Wenn eine Struktur einen Standardkonstruktor definiert, führt die Common Language Runtime beim Erstellen von Arrays der Struktur automatisch den Standardkonstruktor für jedes Arrayelement aus.

Einige Compiler, wie der C # -Compiler, erlauben Strukturen nicht, Standardkonstruktoren zu haben.

Es ist eigentlich ganz einfach und wir werden uns Asimovs Drei Gesetze der Robotik leihen :

  1. Die Struktur muss sicher zu verwenden sein
  2. Die Struktur muss ihre Funktion effizient ausführen, es sei denn, dies würde gegen Regel 1 verstoßen
  3. Die Struktur muss während ihrer Verwendung intakt bleiben, es sei denn, ihre Zerstörung ist erforderlich, um Regel 1 zu erfüllen

... was nehmen wir davon weg : Kurz gesagt, seien Sie verantwortlich für die Verwendung von Werttypen. Sie sind schnell und effizient, können jedoch viele unerwartete Verhaltensweisen verursachen, wenn sie nicht ordnungsgemäß gewartet werden (dh unbeabsichtigte Kopien).


8
Was die Regeln von Microsoft betrifft, so scheint die Regel der Unveränderlichkeit darauf ausgelegt zu sein, die Verwendung von Werttypen so zu unterbinden, dass sich ihr Verhalten von dem der Referenztypen unterscheidet, ungeachtet der Tatsache, dass stückweise veränderbare Wertesemantik nützlich sein kann . Wenn ein Stück stückweise veränderbar wäre, würde dies die Arbeit erleichtern, und wenn Speicherorte des Typs logisch voneinander getrennt werden sollten, sollte der Typ eine "veränderbare" Struktur sein.
Supercat


2
Die Tatsache, dass viele Microsoft-Typen gegen diese Regeln verstoßen, stellt kein Problem mit diesen Typen dar, sondern weist darauf hin, dass die Regeln nicht für alle Strukturtypen gelten sollten. Wenn eine Struktur eine einzelne Entität darstellt [wie mit Decimaloder DateTime], sollte sie durch eine Klasse ersetzt werden, wenn sie die anderen drei Regeln nicht einhält. Wenn eine Struktur eine feste Sammlung von Variablen enthält, von denen jede einen beliebigen Wert enthalten kann, der für ihren Typ gültig wäre [z. B. Rectangle], sollte sie unterschiedliche Regeln einhalten , von denen einige denen für "Einzelwert" -Strukturen widersprechen .
Supercat

4
@IAbstract: Einige Leute würden den DictionaryEintragstyp damit begründen, dass es sich nur um einen internen Typ handelt, die Leistung als wichtiger angesehen wurde als die Semantik oder eine andere Ausrede. Mein Punkt ist, dass ein Typ wie Rectanglesein Inhalt als individuell bearbeitbare Felder angezeigt werden sollte, nicht "weil" die Leistungsvorteile die resultierenden semantischen Unvollkommenheiten überwiegen, sondern weil der Typ semantisch einen festen Satz unabhängiger Werte darstellt und daher die veränderbare Struktur beides ist performanter und semantisch überlegen .
Supercat

2
@supercat: Ich stimme zu ... und der springende Punkt meiner Antwort war, dass die 'Richtlinien' ziemlich schwach sind und Strukturen mit vollem Wissen und Verständnis der Verhaltensweisen verwendet werden sollten. Siehe meine Antwort auf veränderbare Struktur hier: stackoverflow.com/questions/8108920/…
IAbstract

155

Wann immer du:

  1. brauche keinen Polymorphismus,
  2. wollen Wertesemantik, und
  3. Ich möchte die Heap-Zuordnung und den damit verbundenen Aufwand für die Speicherbereinigung vermeiden.

Die Einschränkung besteht jedoch darin, dass die Weitergabe von Strukturen (beliebig groß) teurer ist als Klassenreferenzen (normalerweise ein Maschinenwort), sodass Klassen in der Praxis möglicherweise schneller sind.


1
Das ist nur eine "Einschränkung". Sollte unter anderem auch das "Aufheben" von Werttypen und Fällen in Betracht ziehen (Guid)null(es ist in Ordnung, eine Null in einen Referenztyp umzuwandeln).

1
teurer als in C / C ++? In C ++ wird empfohlen, Objekte nach Wert zu übergeben
Ion Todirel

@IonTodirel War das nicht aus Gründen der Speichersicherheit und nicht aus Gründen der Leistung? Es ist immer ein Kompromiss, aber das Übergeben von 32 B durch Stapel ist immer (TM) langsamer als das Übergeben einer 4 B-Referenz durch Register. Beachten Sie jedoch auch, dass die Verwendung von "Wert / Referenz" in C # und C ++ etwas anders ist. Wenn Sie eine Referenz an ein Objekt übergeben, übergeben Sie immer noch den Wert, obwohl Sie eine Referenz übergeben (Sie ' Übergeben des Wertes der Referenz, im Grunde genommen keine Referenz auf die Referenz). Es ist keine Wertesemantik , aber technisch gesehen "Pass-by-Value".
Luaan

@Luaan Kopieren ist nur ein Aspekt der Kosten. Die zusätzliche Indirektion aufgrund von Zeiger / Referenz kostet ebenfalls pro Zugriff. In einigen Fällen kann die Struktur sogar verschoben werden und muss daher nicht einmal kopiert werden.
Onur

@Onur das ist interessant. Wie "bewegt" man sich ohne zu kopieren? Ich dachte, die Anweisung asm "mov" bewegt sich nicht wirklich. Es kopiert.
Flügelspieler Sendon

148

Ich bin mit den im ursprünglichen Beitrag angegebenen Regeln nicht einverstanden. Hier sind meine Regeln:

1) Sie verwenden Strukturen für die Leistung, wenn sie in Arrays gespeichert sind. (siehe auch Wann sind Strukturen die Antwort? )

2) Sie benötigen sie für den Code, der strukturierte Daten an / von C / C ++ übergibt

3) Verwenden Sie keine Strukturen, es sei denn, Sie benötigen sie:

  • Sie verhalten sich anders als "normale Objekte" ( Referenztypen) unter Zuweisung und Übergabe als Argumente ), was zu unerwartetem Verhalten führen kann. Dies ist besonders gefährlich, wenn die Person, die den Code betrachtet, nicht weiß, dass es sich um eine Struktur handelt.
  • Sie können nicht vererbt werden.
  • Das Übergeben von Strukturen als Argumente ist teurer als Klassen.

4
+1 Ja, ich stimme # 1 voll und ganz zu (dies ist ein großer Vorteil, wenn es um Dinge wie Bilder usw. geht) und um darauf hinzuweisen, dass sie sich von "normalen Objekten" unterscheiden und es eine bekannte Möglichkeit gibt, dies zu wissen, außer durch vorhandenes Wissen oder den Typ selbst untersuchen. Außerdem können Sie keinen Nullwert in einen Strukturtyp umwandeln :-) Dies ist tatsächlich ein Fall, in dem ich mir fast wünschte, es gäbe ein Ungarisch für Nicht-Kern-Werttypen oder ein obligatorisches Schlüsselwort 'struct' an der Site für die Variablendeklaration .

@pst: Es ist wahr, dass man wissen muss, dass etwas ein ist, um structzu wissen, wie es sich verhält, aber wenn etwas ein structmit exponierten Feldern ist, ist das alles, was man wissen muss. Wenn ein Objekt eine Eigenschaft eines Typs für exponierte Feldstrukturen verfügbar macht und Code diese Struktur in eine Variable liest und ändert, kann man sicher vorhersagen, dass eine solche Aktion das Objekt, dessen Eigenschaft gelesen wurde, nicht beeinflusst, es sei denn oder bis die Struktur geschrieben wird zurück. Wenn die Eigenschaft dagegen ein veränderlicher Klassentyp wäre, könnte das Lesen und Ändern das zugrunde liegende Objekt wie erwartet aktualisieren, aber ...
Supercat

... es könnte auch nichts ändern oder Objekte ändern oder beschädigen, die man nicht ändern wollte. Code mit der Semantik "Ändern Sie diese Variable nach Belieben; Änderungen bewirken nichts, bis Sie sie explizit an einem Ort speichern" scheint klarer zu sein als Code mit der Aufschrift "Sie erhalten einen Verweis auf ein Objekt, das möglicherweise mit einer beliebigen Nummer geteilt wird Sie müssen herausfinden, wer möglicherweise noch Verweise auf dieses Objekt hat, um zu wissen, was passieren wird, wenn Sie es ändern. "
Supercat

Genau richtig mit # 1. Eine Liste voller Strukturen kann wesentlich relevantere Daten in L1 / L2-Caches komprimieren als eine Liste voller Objektreferenzen (für die Struktur mit der richtigen Größe).
Matt Stephenson

2
Vererbung ist selten das richtige Werkzeug für den Job, und zu viel über Leistung ohne Profilerstellung nachzudenken ist eine schlechte Idee. Erstens können Strukturen als Referenz übergeben werden. Zweitens ist die Übergabe als Referenz oder Wert selten ein wesentliches Leistungsproblem. Schließlich berücksichtigen Sie nicht die zusätzliche Heap-Zuordnung und Garbage Collection, die für eine Klasse stattfinden muss. Persönlich ziehe ich es vor, Strukturen als einfache alte Daten und Klassen als Dinge zu betrachten, die Dinge (Objekte) tun, obwohl Sie auch Methoden für Strukturen definieren können.
weberc2

88

Verwenden Sie eine Struktur, wenn Sie eine Wertsemantik anstelle einer Referenzsemantik wünschen.

Bearbeiten

Ich bin mir nicht sicher, warum die Leute dies ablehnen, aber dies ist ein gültiger Punkt, der vor der Klärung seiner Frage gemacht wurde und der grundlegendste Grund für eine Struktur ist.

Wenn Sie Referenzsemantik benötigen, benötigen Sie eine Klasse, keine Struktur.


13
Jeder weiss das. Scheint, als suche er mehr als eine Antwort "Struktur ist ein Werttyp".
TheSmurf

21
Es ist der grundlegendste Fall und sollte für jeden angegeben werden, der diesen Beitrag liest und das nicht weiß.
JoshBerke

3
Nicht dass diese Antwort nicht wahr wäre; es ist offensichtlich. Das ist nicht wirklich der Punkt.
TheSmurf

55
@Josh: Für alle, die es noch nicht wissen, ist es eine unzureichende Antwort, einfach zu sagen, da es sehr wahrscheinlich ist, dass sie auch nicht wissen, was es bedeutet.
TheSmurf

1
Ich habe dies gerade abgelehnt, weil ich denke, dass eine der anderen Antworten ganz oben stehen sollte - jede Antwort mit der Aufschrift "Für Interop mit nicht verwaltetem Code, sonst vermeiden".
Daniel Earwicker

59

Neben der Antwort "Es ist ein Wert" besteht ein spezielles Szenario für die Verwendung von Strukturen darin , dass Sie wissen, dass Sie über eine Reihe von Daten verfügen , die Probleme bei der Speicherbereinigung verursachen, und dass Sie viele Objekte haben. Zum Beispiel eine große Liste / ein Array von Personeninstanzen. Die natürliche Metapher hier ist eine Klasse, aber wenn Sie eine große Anzahl langlebiger Personeninstanzen haben, können diese GEN-2 verstopfen und GC-Stände verursachen. Wenn das Szenario dies rechtfertigt, besteht ein möglicher Ansatz darin, ein Array (keine Liste) von Personenstrukturen zu verwenden , d Person[]. H. Anstatt Millionen von Objekten in GEN-2 zu haben, haben Sie jetzt einen einzelnen Block auf dem LOH (ich gehe hier von keinen Zeichenfolgen usw. aus - dh einem reinen Wert ohne Referenzen). Dies hat nur sehr geringe Auswirkungen auf die GC.

Das Arbeiten mit diesen Daten ist umständlich, da die Daten für eine Struktur wahrscheinlich übergroß sind und Sie nicht ständig Fettwerte kopieren möchten. Wenn Sie jedoch direkt in einem Array darauf zugreifen, wird die Struktur nicht kopiert - sie ist vorhanden (im Gegensatz zu einem Listenindexer, der kopiert). Dies bedeutet viel Arbeit mit Indizes:

int index = ...
int id = peopleArray[index].Id;

Beachten Sie, dass es hier hilfreich ist, die Werte selbst unveränderlich zu halten. Verwenden Sie für komplexere Logik eine Methode mit einem by-ref-Parameter:

void Foo(ref Person person) {...}
...
Foo(ref peopleArray[index]);

Auch dies ist vorhanden - wir haben den Wert nicht kopiert.

In sehr spezifischen Szenarien kann diese Taktik sehr erfolgreich sein. Es ist jedoch ein ziemlich fortgeschrittenes Szenario, das nur versucht werden sollte, wenn Sie wissen, was Sie tun und warum. Der Standardwert hier wäre eine Klasse.


+1 Interessante Antwort. Wären Sie bereit, Anekdoten aus der realen Welt über einen solchen Ansatz zu teilen?
Jordão

@ Jordan in auf dem Handy, aber suchen Sie bei Google nach: + gravell + "Angriff von GC"
Marc Gravell

1
Vielen Dank. Ich habe es hier gefunden .
Jordão

2
@MarcGravell Warum haben Sie erwähnt: Verwenden Sie ein Array (keine Liste) ? ListIch glaube, verwendet einen ArrayBlick hinter die Kulissen. Nein ?
Royi Namir

4
@ RoyiNamir Ich war auch neugierig, aber ich glaube, die Antwort liegt im zweiten Absatz von Marc's Antwort. "Wenn Sie jedoch direkt in einem Array darauf zugreifen, wird die Struktur nicht kopiert - sie ist vorhanden (im Gegensatz zu einem Listenindexer, der kopiert)."
user1323245

40

Aus der C # -Sprachenspezifikation :

1.7 Strukturen

Strukturen sind wie Klassen Datenstrukturen, die Datenelemente und Funktionselemente enthalten können. Im Gegensatz zu Klassen sind Strukturen jedoch Werttypen und erfordern keine Heapzuweisung. Eine Variable eines Strukturtyps speichert direkt die Daten der Struktur, während eine Variable eines Klassentyps einen Verweis auf ein dynamisch zugewiesenes Objekt speichert. Strukturtypen unterstützen keine benutzerdefinierte Vererbung, und alle Strukturtypen erben implizit vom Typobjekt.

Strukturen sind besonders nützlich für kleine Datenstrukturen mit Wertsemantik. Komplexe Zahlen, Punkte in einem Koordinatensystem oder Schlüssel-Wert-Paare in einem Wörterbuch sind gute Beispiele für Strukturen. Die Verwendung von Strukturen anstelle von Klassen für kleine Datenstrukturen kann einen großen Unterschied in der Anzahl der Speicherzuweisungen bewirken, die eine Anwendung ausführt. Das folgende Programm erstellt und initialisiert beispielsweise ein Array mit 100 Punkten. Wenn Point als Klasse implementiert ist, werden 101 separate Objekte instanziiert - eines für das Array und jeweils eines für die 100 Elemente.

class Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

class Test
{
   static void Main() {
      Point[] points = new Point[100];
      for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
   }
}

Eine Alternative besteht darin, Point zu einer Struktur zu machen.

struct Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

Jetzt wird nur ein Objekt instanziiert - das für das Array - und die Point-Instanzen werden inline im Array gespeichert.

Strukturkonstruktoren werden mit dem neuen Operator aufgerufen, dies bedeutet jedoch nicht, dass Speicher zugewiesen wird. Anstatt ein Objekt dynamisch zuzuweisen und einen Verweis darauf zurückzugeben, gibt ein Strukturkonstruktor einfach den Strukturwert selbst zurück (normalerweise an einem temporären Speicherort auf dem Stapel), und dieser Wert wird dann nach Bedarf kopiert.

Bei Klassen ist es möglich, dass zwei Variablen auf dasselbe Objekt verweisen, und dass Operationen an einer Variablen das Objekt beeinflussen, auf das die andere Variable verweist. Bei Strukturen haben die Variablen jeweils eine eigene Kopie der Daten, und es ist nicht möglich, dass Operationen an einer die andere beeinflussen. Die vom folgenden Codefragment erzeugte Ausgabe hängt beispielsweise davon ab, ob Point eine Klasse oder eine Struktur ist.

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Wenn Point eine Klasse ist, ist die Ausgabe 20, da a und b auf dasselbe Objekt verweisen. Wenn Point eine Struktur ist, ist die Ausgabe 10, da durch die Zuweisung von a zu b eine Kopie des Werts erstellt wird und diese Kopie von der nachfolgenden Zuweisung zu ax nicht betroffen ist

Das vorherige Beispiel zeigt zwei der Einschränkungen von Strukturen. Erstens ist das Kopieren einer gesamten Struktur in der Regel weniger effizient als das Kopieren einer Objektreferenz, sodass die Zuweisung und Übergabe von Wertparametern bei Strukturen teurer sein kann als bei Referenztypen. Zweitens ist es mit Ausnahme der Parameter ref und out nicht möglich, Verweise auf Strukturen zu erstellen, wodurch deren Verwendung in einer Reihe von Situationen ausgeschlossen wird.


4
Die Tatsache, dass Verweise auf Strukturen nicht beibehalten werden können, ist manchmal eine Einschränkung, aber auch ein sehr nützliches Merkmal. Eine der Hauptschwächen von .net besteht darin, dass es keinen vernünftigen Weg gibt, außerhalb des Codes einen Verweis auf ein veränderliches Objekt zu übergeben, ohne für immer die Kontrolle über dieses Objekt zu verlieren. Im Gegensatz dazu kann man refeiner veränderlichen Struktur sicher eine externe Methode a geben und wissen, dass alle Mutationen, die diese externe Methode auf ihr ausführt, durchgeführt werden, bevor sie zurückkehren. Es ist schade, dass .net kein Konzept für kurzlebige Parameter und Funktionsrückgabewerte hat, da ...
Supercat

4
... damit die vorteilhafte Semantik der übergebenen Strukturen refmit Klassenobjekten erreicht werden kann. Im Wesentlichen können lokale Variablen, Parameter und Funktionsrückgabewerte dauerhaft (Standard), zurückgabbar oder kurzlebig sein. Es wäre dem Code verboten, kurzlebige Dinge in alles zu kopieren, was den gegenwärtigen Umfang überleben würde. Mehrwegartikel wären wie vergängliche Dinge, außer dass sie von einer Funktion zurückgegeben werden könnten. Der Rückgabewert einer Funktion würde an die strengsten Einschränkungen gebunden sein, die für einen ihrer "rückgabbaren" Parameter gelten.
Supercat

34

Strukturen eignen sich zur atomaren Darstellung von Daten, wobei diese Daten vom Code mehrfach kopiert werden können. Das Klonen eines Objekts ist im Allgemeinen teurer als das Kopieren einer Struktur, da der Speicher zugewiesen, der Konstruktor ausgeführt und die Freigabe / Speicherbereinigung freigegeben wird, wenn dies erledigt ist.


4
Ja, aber große Strukturen können teurer sein als Klassenreferenzen (wenn sie an Methoden weitergegeben werden).
Alex

27

Hier ist eine Grundregel.

  • Wenn alle Mitgliedsfelder Werttypen sind, erstellen Sie eine Struktur .

  • Wenn ein Elementfeld ein Referenztyp ist, erstellen Sie eine Klasse . Dies liegt daran, dass das Feld für den Referenztyp ohnehin die Heap-Zuordnung benötigt.

Beispiele

public struct MyPoint 
{
    public int X; // Value Type
    public int Y; // Value Type
}

public class MyPointWithName 
{
    public int X; // Value Type
    public int Y; // Value Type
    public string Name; // Reference Type
}

3
Unveränderliche Referenztypen wie stringsind semantisch äquivalent zu Werten, und das Speichern einer Referenz auf ein unveränderliches Objekt in einem Feld erfordert keine Heap-Zuordnung. Der Unterschied zwischen einer Struktur mit sichtbaren öffentlichen Bereichen und ein Klassenobjekt mit sichtbaren öffentlichen Bereichen ist , dass die Codefolge gegeben var q=p; p.X=4; q.X=5;, p.Xden Wert 4 hat , wenn aein Strukturtyp ist, und 5 , wenn es dich um einen Klassentyp ist. Wenn man die Elemente des Typs bequem ändern möchte, sollte man "Klasse" oder "Struktur" auswählen, je nachdem, ob Änderungen qwirksam werden sollen p.
Supercat

Ja, ich bin damit einverstanden, dass sich die Referenzvariable auf dem Stapel befindet, aber das Objekt, auf das sie verweist, auf dem Heap vorhanden ist. Strukturen und Klassen verhalten sich zwar unterschiedlich, wenn sie einer anderen Variablen zugewiesen werden, aber ich denke nicht, dass dies ein entscheidender Faktor ist.
Usman Zafar

Veränderbare Strukturen und veränderliche Klassen verhalten sich völlig unterschiedlich. Wenn einer richtig ist, wird der andere höchstwahrscheinlich falsch sein. Ich bin mir nicht sicher, wie das Verhalten kein entscheidender Faktor für die Verwendung einer Struktur oder einer Klasse sein würde.
Supercat

Ich sagte, dass dies kein entscheidender Faktor ist, da Sie beim Erstellen einer Klasse oder Struktur häufig nicht sicher sind, wie sie verwendet werden soll. Sie konzentrieren sich also darauf, wie die Dinge aus gestalterischer Sicht sinnvoller sind. Jedenfalls habe ich noch nie eine einzige Stelle in der .NET-Bibliothek gesehen, an der eine Struktur eine Referenzvariable enthält.
Usman Zafar

1
Der Strukturtyp ArraySegment<T>kapselt a T[], was immer ein Klassentyp ist. Der Strukturtyp KeyValuePair<TKey,TValue>wird häufig mit Klassentypen als generische Parameter verwendet.
Supercat

19

Erstens: Interop-Szenarien oder wenn Sie das Speicherlayout angeben müssen

Zweitens: Wenn die Daten sowieso fast die gleiche Größe wie ein Referenzzeiger haben.


17

Sie müssen eine "Struktur" in Situationen verwenden, in denen Sie das Speicherlayout mithilfe des StructLayoutAttribute explizit angeben möchten - normalerweise für PInvoke.

Bearbeiten: Kommentar weist darauf hin, dass Sie mit StructLayoutAttribute Klasse oder Struktur verwenden können, und das ist sicherlich richtig. In der Praxis verwenden Sie normalerweise eine Struktur - sie wird auf dem Stapel gegenüber dem Heap zugewiesen. Dies ist sinnvoll, wenn Sie nur ein Argument an einen nicht verwalteten Methodenaufruf übergeben.


5
Das StructLayoutAttribute kann auf Strukturen oder Klassen angewendet werden, daher ist dies kein Grund, Strukturen zu verwenden.
Stephen Martin

Warum ist es sinnvoll, wenn Sie nur ein Argument an einen nicht verwalteten Methodenaufruf übergeben?
David Klempfner

16

Ich benutze Strukturen zum Packen oder Entpacken von binären Kommunikationsformaten. Dies umfasst das Lesen oder Schreiben auf die Festplatte, DirectX-Scheitelpunktlisten, Netzwerkprotokolle oder den Umgang mit verschlüsselten / komprimierten Daten.

Die drei Richtlinien, die Sie auflisten, waren in diesem Zusammenhang für mich nicht hilfreich. Wenn ich vierhundert Bytes in einer bestimmten Reihenfolge schreiben muss, werde ich eine vierhundert-Byte-Struktur definieren und sie mit allen nicht verwandten Werten füllen, die sie haben soll, und ich werde gehen es so einzurichten, wie es zu der Zeit am sinnvollsten ist. (Okay, vierhundert Bytes wären ziemlich seltsam - aber als ich meinen Lebensunterhalt mit Excel-Dateien verdient habe, habe ich mich mit Strukturen von bis zu vierzig Bytes beschäftigt, denn so groß sind einige der BIFF-Datensätze.)


Könnten Sie dafür nicht genauso einfach einen Referenztyp verwenden?
David Klempfner

15

Mit Ausnahme der Werttypen, die direkt von der Laufzeit und verschiedenen anderen für PInvoke-Zwecke verwendet werden, sollten Sie Werttypen nur in zwei Szenarien verwenden.

  1. Wenn Sie eine Kopiersemantik benötigen.
  2. Wenn Sie eine automatische Initialisierung benötigen, normalerweise in Arrays dieser Typen.

# 2 scheint Teil des Grundes für die Strukturprävalenz in .Net-Sammlungsklassen zu sein.
IAbstract

Wenn das erste, was Sie beim Erstellen eines Speicherorts eines Klassentyps tun würden, wäre, eine neue Instanz dieses Typs zu erstellen, einen Verweis darauf an diesem Speicherort zu speichern und den Verweis niemals irgendwo anders zu kopieren oder zu überschreiben, dann eine Struktur und Klasse würde sich identisch verhalten. Strukturen bieten eine bequeme Standardmethode zum Kopieren aller Felder von einer Instanz in eine andere und bieten im Allgemeinen eine bessere Leistung in Fällen, in denen ein Verweis auf eine Klasse niemals dupliziert wird (mit Ausnahme des kurzlebigen thisParameters, der zum Aufrufen ihrer Methoden verwendet wird). Klassen ermöglichen das Duplizieren von Referenzen.
Supercat

13

.NET unterstützt value typesund reference types(in Java können Sie nur Referenztypen definieren). Instanzen von reference typeswerden im verwalteten Heap zugewiesen und werden mit Müll gesammelt, wenn keine ausstehenden Verweise darauf vorhanden sind. Instanzen von value typeswerden andererseits im zugeordnet stack, und daher wird der zugewiesene Speicher zurückgefordert, sobald ihr Umfang endet. Und natürlich werden value typesSie nach Wert und reference typesReferenz übergeben. Alle primitiven C # -Datentypen mit Ausnahme von System.String sind Werttypen.

Wann wird struct over class verwendet?

In C # structssind value typesKlassen reference types. Sie können Werttypen in C # mit dem enumSchlüsselwort und dem structSchlüsselwort erstellen . Die Verwendung von a value typeanstelle von a reference typeführt zu weniger Objekten auf dem verwalteten Heap, was zu einer geringeren Belastung des Garbage Collector (GC), weniger häufigen GC-Zyklen und folglich zu einer besseren Leistung führt. Allerdings value typeshaben auch ihre Schattenseiten. Das structWeitergeben eines großen ist definitiv teurer als das Weitergeben einer Referenz, das ist ein offensichtliches Problem. Das andere Problem ist der damit verbundene Overhead boxing/unboxing. Wenn Sie sich fragen, was das boxing/unboxingbedeutet, folgen Sie diesen Links, um eine gute Erklärung zu boxingund zu erhaltenunboxing. Abgesehen von der Leistung gibt es Zeiten, in denen Sie einfach Typen benötigen, um eine Wertsemantik zu haben, deren Implementierung sehr schwierig (oder hässlich) wäre, wenn Sie nur über eine solche reference typesverfügen. Sie sollten value typesnur verwenden, wenn Sie eine Kopiersemantik oder eine automatische Initialisierung benötigen, normalerweise in arraysdiesen Typen.


Das Kopieren kleiner Strukturen oder das Übergeben von Werten ist so billig wie das Kopieren oder Übergeben einer Klassenreferenz oder das Übergeben von Strukturen ref. Das Übergeben einer beliebigen Größenstruktur refkostet genauso viel wie das Übergeben einer Klassenreferenz nach Wert. Das Kopieren einer Struktur beliebiger Größe oder das Übergeben von Werten ist billiger als das Ausführen einer defensiven Kopie eines Klassenobjekts und das Speichern oder Übergeben eines Verweises darauf. Die großen Zeiten, in denen Klassen besser sind als Strukturen zum Speichern von Werten, sind (1), wenn die Klassen unveränderlich sind (um defensives Kopieren zu vermeiden), und jede erstellte Instanz wird viel
herumgereicht

... (2) wenn aus verschiedenen Gründen eine Struktur einfach nicht verwendbar wäre [z. B. weil man verschachtelte Referenzen für so etwas wie einen Baum verwenden muss oder weil man Polymorphismus benötigt]. Beachten Sie, dass bei der Verwendung von Werttypen Felder im Allgemeinen direkt ohne einen bestimmten Grund verfügbar gemacht werden sollten (während bei den meisten Klassentypen Felder in Eigenschaften eingeschlossen werden sollten). Viele der sogenannten "Übel" veränderlicher
Werttypen resultieren

... das Richtige tun, alle Compiler würden Versuche, Felder in solchen Strukturen direkt festzulegen, ordnungsgemäß ablehnen. Der beste Weg, um sicherzustellen, dass Compiler ablehnen, readOnlyStruct.someMember = 5;besteht darin, someMemberkeine schreibgeschützte Eigenschaft zu erstellen, sondern sie zu einem Feld zu machen.
Supercat

12

Eine Struktur ist ein Werttyp. Wenn Sie einer neuen Variablen eine Struktur zuweisen, enthält die neue Variable eine Kopie des Originals.

public struct IntStruct {
    public int Value {get; set;}
}

Die Ausführung der folgenden Ergebnisse führt zu 5 Instanzen der im Speicher gespeicherten Struktur:

var struct1 = new IntStruct() { Value = 0 }; // original
var struct2 = struct1;  // A copy is made
var struct3 = struct2;  // A copy is made
var struct4 = struct3;  // A copy is made
var struct5 = struct4;  // A copy is made

// NOTE: A "copy" will occur when you pass a struct into a method parameter.
// To avoid the "copy", use the ref keyword.

// Although structs are designed to use less system resources
// than classes.  If used incorrectly, they could use significantly more.

Eine Klasse ist ein Referenztyp. Wenn Sie einer neuen Variablen eine Klasse zuweisen, enthält die Variable einen Verweis auf das ursprüngliche Klassenobjekt.

public class IntClass {
    public int Value {get; set;}
}

Die Ausführung des Folgenden führt nur zu einer Instanz des Klassenobjekts im Speicher.

var class1 = new IntClass() { Value = 0 };
var class2 = class1;  // A reference is made to class1
var class3 = class2;  // A reference is made to class1
var class4 = class3;  // A reference is made to class1
var class5 = class4;  // A reference is made to class1  

Strukturen können die Wahrscheinlichkeit eines Codefehlers erhöhen. Wenn ein Wertobjekt wie ein veränderbares Referenzobjekt behandelt wird, kann ein Entwickler überrascht sein, wenn die vorgenommenen Änderungen unerwartet verloren gehen.

var struct1 = new IntStruct() { Value = 0 };
var struct2 = struct1;
struct2.Value = 1;
// At this point, a developer may be surprised when 
// struct1.Value is 0 and not 1

12

Ich habe mit BenchmarkDotNet einen kleinen Benchmark erstellt , um den "Struktur" -Vorteil in Zahlen besser zu verstehen. Ich teste das Durchlaufen eines Arrays (oder einer Liste) von Strukturen (oder Klassen). Das Erstellen dieser Arrays oder Listen liegt außerhalb des Bereichs des Benchmarks. Es ist klar, dass "Klasse" schwerer ist, mehr Speicher benötigt und GC umfasst.

Die Schlussfolgerung lautet also: Seien Sie vorsichtig mit LINQ und versteckten Strukturen beim Boxen / Entpacken und verwenden Sie Strukturen für Mikrooptimierungen ausschließlich bei Arrays.

PS Ein weiterer Maßstab für die Übergabe von Struktur / Klasse durch den Aufrufstapel ist https://stackoverflow.com/a/47864451/506147

BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Core   : .NET Core 4.6.25211.01, 64bit RyuJIT


          Method |  Job | Runtime |      Mean |     Error |    StdDev |       Min |       Max |    Median | Rank |  Gen 0 | Allocated |
---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
   TestListClass |  Clr |     Clr |  5.599 us | 0.0408 us | 0.0382 us |  5.561 us |  5.689 us |  5.583 us |    3 |      - |       0 B |
  TestArrayClass |  Clr |     Clr |  2.024 us | 0.0102 us | 0.0096 us |  2.011 us |  2.043 us |  2.022 us |    2 |      - |       0 B |
  TestListStruct |  Clr |     Clr |  8.427 us | 0.1983 us | 0.2204 us |  8.101 us |  9.007 us |  8.374 us |    5 |      - |       0 B |
 TestArrayStruct |  Clr |     Clr |  1.539 us | 0.0295 us | 0.0276 us |  1.502 us |  1.577 us |  1.537 us |    1 |      - |       0 B |
   TestLinqClass |  Clr |     Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us |    7 | 0.0153 |      80 B |
  TestLinqStruct |  Clr |     Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us |    9 |      - |      96 B |
   TestListClass | Core |    Core |  5.747 us | 0.1147 us | 0.1275 us |  5.567 us |  5.945 us |  5.756 us |    4 |      - |       0 B |
  TestArrayClass | Core |    Core |  2.023 us | 0.0299 us | 0.0279 us |  1.990 us |  2.069 us |  2.013 us |    2 |      - |       0 B |
  TestListStruct | Core |    Core |  8.753 us | 0.1659 us | 0.1910 us |  8.498 us |  9.110 us |  8.670 us |    6 |      - |       0 B |
 TestArrayStruct | Core |    Core |  1.552 us | 0.0307 us | 0.0377 us |  1.496 us |  1.618 us |  1.552 us |    1 |      - |       0 B |
   TestLinqClass | Core |    Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us |    8 | 0.0153 |      72 B |
  TestLinqStruct | Core |    Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us |   10 |      - |      88 B |

Code:

[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkRef
    {
        public class C1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        public struct S1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        List<C1> testListClass = new List<C1>();
        List<S1> testListStruct = new List<S1>();
        C1[] testArrayClass;
        S1[] testArrayStruct;
        public BenchmarkRef()
        {
            for(int i=0;i<1000;i++)
            {
                testListClass.Add(new C1  { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
                testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
            }
            testArrayClass = testListClass.ToArray();
            testArrayStruct = testListStruct.ToArray();
        }

        [Benchmark]
        public int TestListClass()
        {
            var x = 0;
            foreach(var i in testListClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayClass()
        {
            var x = 0;
            foreach (var i in testArrayClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestListStruct()
        {
            var x = 0;
            foreach (var i in testListStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayStruct()
        {
            var x = 0;
            foreach (var i in testArrayStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestLinqClass()
        {
            var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }

        [Benchmark]
        public int TestLinqStruct()
        {
            var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }
    }

Haben Sie herausgefunden, warum Strukturen in Listen und dergleichen so viel langsamer sind? Liegt es an dem versteckten Boxen und Unboxing, das Sie erwähnt haben? Wenn ja, warum passiert es?
Marko Grdinic

Der Zugriff auf struct im Array sollte schneller sein, da keine zusätzliche Referenzierung erforderlich ist. Boxen / Unboxen ist bei linq der Fall.
Roman Pokrovskij

10

Strukturtypen in C # oder anderen .net-Sprachen werden im Allgemeinen verwendet, um Dinge zu speichern, die sich wie Wertegruppen mit fester Größe verhalten sollten. Ein nützlicher Aspekt von Strukturtypen besteht darin, dass die Felder einer Strukturtypinstanz geändert werden können, indem der Speicherort, in dem sie gespeichert ist, geändert wird, und auf keine andere Weise. Es ist möglich, eine Struktur so zu codieren, dass die einzige Möglichkeit, ein Feld zu mutieren, darin besteht, eine ganz neue Instanz zu erstellen und dann mithilfe einer Strukturzuweisung alle Felder des Ziels zu mutieren, indem sie mit Werten aus der neuen Instanz überschrieben werden Sofern eine Struktur keine Möglichkeit bietet, eine Instanz zu erstellen, in der ihre Felder nicht standardmäßige Werte haben, sind alle Felder veränderbar, wenn die Struktur selbst an einem veränderlichen Speicherort gespeichert ist.

Beachten Sie, dass es möglich ist, einen Strukturtyp so zu gestalten, dass er sich im Wesentlichen wie ein Klassentyp verhält, wenn die Struktur ein privates Feld vom Typ Klasse enthält und seine eigenen Elemente zu dem des umschlossenen Klassenobjekts umleitet. Zum Beispiel PersonCollectionkönnte a Eigenschaften anbieten SortedByNameund SortedById, die beide einen "unveränderlichen" Verweis auf a enthalten PersonCollection(in ihrem Konstruktor festgelegt) und GetEnumeratordurch Aufrufen von entweder creator.GetNameSortedEnumeratoroder implementieren creator.GetIdSortedEnumerator. Solche Strukturen würden sich ähnlich wie ein Verweis auf a verhalten PersonCollection, außer dass ihre GetEnumeratorMethoden an verschiedene Methoden in der gebunden wären PersonCollection. Man könnte auch eine Struktur einen Teil eines Arrays umschließen lassen (z. B. könnte man eine ArrayRange<T>Struktur definieren , die einen T[]aufgerufenen Arr, einen int enthältOffset und einen int enthältLengthmit einer indizierten Eigenschaft, auf die für einen Index idxim Bereich von 0 bis Length-1zugegriffen werden würde Arr[idx+Offset]). Wenn fooes sich um eine schreibgeschützte Instanz einer solchen Struktur handelt, erlauben aktuelle Compilerversionen leider keine Operationen wie, foo[3]+=4;da sie nicht bestimmen können, ob solche Operationen versuchen würden, in Felder von zu schreiben foo.

Es ist auch möglich, eine Struktur so zu entwerfen, dass sie sich wie ein Werttyp verhält, der eine Sammlung mit variabler Größe enthält (die scheinbar immer dann kopiert wird, wenn sich die Struktur befindet). Die einzige Möglichkeit, dies zu erreichen, besteht darin, sicherzustellen, dass kein Objekt vorhanden ist, für das die struct enthält eine Referenz, die jemals irgendetwas ausgesetzt sein wird, das sie mutieren könnte. Zum Beispiel könnte man eine Array-ähnliche Struktur haben, die ein privates Array enthält und deren indizierte "put" -Methode ein neues Array erstellt, dessen Inhalt bis auf ein geändertes Element dem des Originals entspricht. Leider kann es etwas schwierig sein, solche Strukturen effizient arbeiten zu lassen. Es gibt zwar Zeiten, in denen die Struktur-Semantik praktisch sein kann (z. B. die Möglichkeit, eine Array-ähnliche Sammlung an eine Routine zu übergeben, wobei sowohl der Aufrufer als auch der Angerufene wissen, dass externer Code die Sammlung nicht ändert).


10

Nein, ich stimme den Regeln nicht ganz zu. Sie sind gute Richtlinien, die bei Leistung und Standardisierung zu berücksichtigen sind, jedoch nicht im Hinblick auf die Möglichkeiten.

Wie Sie in den Antworten sehen können, gibt es viele kreative Möglichkeiten, sie zu verwenden. Diese Richtlinien müssen also genau das sein, immer aus Gründen der Leistung und Effizienz.

In diesem Fall verwende ich Klassen, um Objekte der realen Welt in ihrer größeren Form darzustellen. Ich verwende Strukturen, um kleinere Objekte darzustellen, die genauere Verwendungszwecke haben. So wie du es gesagt hast, "ein zusammenhängenderes Ganzes". Das Schlüsselwort ist zusammenhängend. Die Klassen sind eher objektorientierte Elemente, während Strukturen einige dieser Eigenschaften aufweisen können, wenn auch in kleinerem Maßstab. IMO.

Ich verwende sie häufig in Treeview- und Listview-Tags, auf die sehr schnell auf allgemeine statische Attribute zugegriffen werden kann. Ich habe immer versucht, diese Informationen auf andere Weise zu erhalten. In meinen Datenbankanwendungen verwende ich beispielsweise eine Baumansicht, in der ich Tabellen, SPs, Funktionen oder andere Objekte habe. Ich erstelle und fülle meine Struktur, füge sie in das Tag ein, ziehe sie heraus, erhalte die Daten der Auswahl und so weiter. Ich würde das nicht mit einer Klasse machen!

Ich versuche, sie klein zu halten, sie in Einzelsituationen zu verwenden und zu verhindern, dass sie sich ändern. Es ist ratsam, sich des Speichers, der Zuordnung und der Leistung bewusst zu sein. Und das Testen ist so notwendig.


Strukturen können sinnvoll verwendet werden, um leichte unveränderliche Objekte darzustellen, oder sie können sinnvoll verwendet werden, um feste Mengen verwandter, aber unabhängiger Variablen (z. B. die Koordinaten eines Punktes) darzustellen. Der Rat auf dieser Seite ist gut für Strukturen, die dem ersteren Zweck dienen sollen, aber falsch für Strukturen, die dem letzteren Zweck dienen sollen. Mein gegenwärtiger Gedanke ist, dass Strukturen, die private Felder haben, im Allgemeinen der angegebenen Beschreibung entsprechen sollten, aber viele Strukturen sollten ihren gesamten Zustand über öffentliche Felder verfügbar machen.
Supercat

Wenn die Spezifikation für einen "3D-Punkt" -Typ angibt, dass sein gesamter Status über lesbare Elemente x, y und z verfügbar ist, und es möglich ist, eine Instanz mit einer beliebigen Kombination von doubleWerten für diese Koordinaten zu erstellen, würde eine solche Spezifikation sie dazu zwingen Verhalten Sie sich semantisch identisch mit einer exponierten Feldstruktur, mit Ausnahme einiger Details des Multithread-Verhaltens (die unveränderliche Klasse wäre in einigen Fällen besser, während die exponierte Feldstruktur in anderen Fällen besser wäre; eine sogenannte "unveränderliche" Struktur würde dies tun in jedem Fall schlechter sein).
Supercat

8

Meine Regel ist

1, benutze immer Klasse;

2, Wenn es ein Leistungsproblem gibt, versuche ich, eine Klasse in Abhängigkeit von den von @IAbstract erwähnten Regeln in struct zu ändern, und mache dann einen Test, um festzustellen, ob diese Änderungen die Leistung verbessern können.


Ein wesentlicher Anwendungsfall, den Microsoft ignoriert, besteht darin, dass eine Variable vom Typ Fooeine feste Sammlung unabhängiger Werte (z. B. Koordinaten eines Punkts) kapseln soll, die manchmal als Gruppe weitergegeben und manchmal unabhängig geändert werden soll. Ich habe kein Muster für die Verwendung von Klassen gefunden, das beide Zwecke fast so gut kombiniert wie eine einfache Exposed-Field-Struktur (die als feste Sammlung unabhängiger Variablen perfekt zur Rechnung passt).
Supercat

1
@supercat: Ich denke, es ist nicht ganz fair, Microsoft dafür verantwortlich zu machen. Das eigentliche Problem hierbei ist, dass sich C # als objektorientierte Sprache einfach nicht auf einfache Datensatztypen konzentriert, die nur Daten ohne viel Verhalten verfügbar machen. C # ist keine Multi-Paradigmen-Sprache in dem Maße wie z. B. C ++. Abgesehen davon glaube ich auch, dass nur sehr wenige Leute reines OOP programmieren, also ist C # vielleicht eine zu idealistische Sprache. (Ich public readonly
jedenfalls

1
@stakx: Es ist nicht nötig, sich auf solche Typen zu "konzentrieren"; es würde ausreichen, sie als das zu erkennen, was sie sind. Die größte Schwäche von C # in Bezug auf Strukturen ist das größte Problem auch in vielen anderen Bereichen: Die Sprache bietet unzureichende Möglichkeiten, um anzuzeigen, wann bestimmte Transformationen angemessen sind oder nicht, und das Fehlen solcher Einrichtungen führt zu unglücklichen Entwurfsentscheidungen. Zum Beispiel stammen 99% der "veränderlichen Strukturen sind böse" aus der Umwandlung des Compilers MyListOfPoint[3].Offset(2,3);in var temp=MyListOfPoint[3]; temp.Offset(2,3);eine Transformation, die bei Anwendung falsch ist ...
Supercat

... zur OffsetMethode. Der richtige Weg, um solchen falschen Code zu verhindern, sollte nicht darin bestehen, Strukturen unnötig unveränderlich zu machen, sondern Methoden zuzulassen Offset, die mit einem Attribut versehen werden sollen, das die oben erwähnte Transformation verbietet. Auch implizite numerische Konvertierungen hätten viel besser sein können, wenn sie so markiert worden wären, dass sie nur in Fällen anwendbar sind, in denen ihr Aufruf offensichtlich wäre. Wenn für foo(float,float)und Überladungen vorhanden sind foo(double,double), würde ich davon ausgehen, dass der Versuch, a floatund a zu verwenden, doublehäufig keine implizite Konvertierung anwenden sollte, sondern stattdessen ein Fehler sein sollte.
Supercat

Eine direkte Zuweisung eines doubleWerts zu a floatoder die Übergabe an eine Methode, die ein floatArgument annehmen kann, aber nicht double, würde fast immer das tun, was der Programmierer beabsichtigt hat. Im Gegensatz dazu ist das Zuweisen eines floatAusdrucks doubleohne explizite Typumwandlung oft ein Fehler. Die implizite double->floatKonvertierung würde nur dann zu Problemen führen, wenn eine weniger als ideale Überlastung ausgewählt würde. Ich würde davon ausgehen, dass der richtige Weg, dies zu verhindern, nicht darin bestehen sollte, implizites Double-> Float zu verbieten, sondern Überladungen mit Attributen zu versehen, um die Konvertierung nicht zuzulassen.
Supercat

8

Eine Klasse ist ein Referenztyp. Wenn ein Objekt der Klasse erstellt wird, enthält die Variable, der das Objekt zugewiesen ist, nur einen Verweis auf diesen Speicher. Wenn die Objektreferenz einer neuen Variablen zugewiesen wird, bezieht sich die neue Variable auf das ursprüngliche Objekt. Über eine Variable vorgenommene Änderungen werden in der anderen Variablen wiedergegeben, da beide auf dieselben Daten verweisen. Eine Struktur ist ein Werttyp. Wenn eine Struktur erstellt wird, enthält die Variable, der die Struktur zugewiesen ist, die tatsächlichen Daten der Struktur. Wenn die Struktur einer neuen Variablen zugewiesen wird, wird sie kopiert. Die neue Variable und die ursprüngliche Variable enthalten daher zwei separate Kopien derselben Daten. Änderungen an einer Kopie wirken sich nicht auf die andere Kopie aus. Im Allgemeinen werden Klassen verwendet, um komplexeres Verhalten oder Daten zu modellieren, die nach dem Erstellen eines Klassenobjekts geändert werden sollen.

Klassen und Strukturen (C # -Programmierhandbuch)


Strukturen sind auch in Fällen sehr gut, in denen es erforderlich ist, einige verwandte, aber unabhängige Variablen zusammen mit Klebeband (z. B. die Koordinaten eines Punktes) zu befestigen. Die MSDN-Richtlinien sind sinnvoll, wenn versucht wird, Strukturen zu erstellen, die sich wie Objekte verhalten, aber beim Entwerfen von Aggregaten weitaus weniger geeignet sind. Einige von ihnen sind in letzterer Situation fast genau falsch . Je größer beispielsweise der Grad der Unabhängigkeit der von einem Typ eingekapselten Variablen ist, desto größer ist der Vorteil der Verwendung einer exponierten Feldstruktur anstelle einer unveränderlichen Klasse.
Superkatze

6

MYTHOS 1: STRUKTE SIND LEICHTE KLASSEN

Dieser Mythos kommt in verschiedenen Formen vor. Einige Leute glauben, dass Werttypen keine Methoden oder anderes signifikantes Verhalten haben können oder sollten - sie sollten als einfache Datenübertragungstypen mit nur öffentlichen Feldern oder einfachen Eigenschaften verwendet werden. Der DateTime-Typ ist ein gutes Gegenbeispiel dazu: Es ist sinnvoll, dass er ein Werttyp ist, da er eine grundlegende Einheit wie eine Zahl oder ein Zeichen ist, und es ist auch sinnvoll, Berechnungen basierend auf durchführen zu können dessen Wert. Wenn man die Dinge aus der anderen Richtung betrachtet, sollten Datenübertragungstypen oft ohnehin Referenztypen sein - die Entscheidung sollte auf dem gewünschten Wert oder der Referenztypsemantik basieren, nicht auf der Einfachheit des Typs. Andere Leute glauben, dass Werttypen in Bezug auf die Leistung „leichter“ sind als Referenztypen. Die Wahrheit ist, dass Werttypen in einigen Fällen leistungsfähiger sind - sie erfordern keine Speicherbereinigung, es sei denn, sie sind in Kästchen verpackt, haben keinen Typidentifizierungsaufwand und erfordern beispielsweise keine Dereferenzierung. Auf andere Weise sind Referenztypen jedoch leistungsfähiger: Das Übergeben von Parametern, das Zuweisen von Werten zu Variablen, das Zurückgeben von Werten und ähnliche Vorgänge erfordern nur das Kopieren von 4 oder 8 Byte (je nachdem, ob Sie die 32-Bit- oder die 64-Bit-CLR ausführen ) anstatt alle Daten zu kopieren. Stellen Sie sich vor, ArrayList wäre irgendwie ein „reiner“ Wertetyp, und wenn Sie einen ArrayList-Ausdruck an eine Methode übergeben, müssen Sie alle Daten kopieren! In fast allen Fällen wird die Leistung ohnehin nicht wirklich durch diese Art von Entscheidung bestimmt. Engpässe treten fast nie dort auf, wo Sie glauben, und bevor Sie eine auf der Leistung basierende Entwurfsentscheidung treffen, Sie sollten die verschiedenen Optionen messen. Es ist erwähnenswert, dass die Kombination der beiden Überzeugungen auch nicht funktioniert. Es spielt keine Rolle, wie viele Methoden ein Typ hat (ob es sich um eine Klasse oder eine Struktur handelt) - der pro Instanz belegte Speicher ist nicht betroffen. (Es gibt Kosten in Bezug auf den Speicher, der für den Code selbst beansprucht wird, aber dieser fällt einmal und nicht für jede Instanz an.)

MYTHOS 2: REFERENZTYPEN LEBEN AUF DEM KOPF; Werttypen leben auf dem Stapel

Dieser wird oft durch Faulheit der Person verursacht, die ihn wiederholt. Der erste Teil ist korrekt - eine Instanz eines Referenztyps wird immer auf dem Heap erstellt. Es ist der zweite Teil, der Probleme verursacht. Wie bereits erwähnt, lebt der Wert einer Variablen überall dort, wo sie deklariert ist. Wenn Sie also eine Klasse mit einer Instanzvariablen vom Typ int haben, befindet sich der Wert dieser Variablen für ein bestimmtes Objekt immer dort, wo sich der Rest der Daten für das Objekt befindet. auf dem Haufen. Auf dem Stapel befinden sich nur lokale Variablen (innerhalb von Methoden deklarierte Variablen) und Methodenparameter. In C # 2 und höher leben sogar einige lokale Variablen nicht wirklich auf dem Stapel, wie Sie sehen werden, wenn wir uns anonyme Methoden in Kapitel 5 ansehen. SIND DIESE KONZEPTE JETZT RELEVANT? Wenn Sie verwalteten Code schreiben, sollten Sie sich zur Laufzeit Gedanken darüber machen, wie der Speicher am besten genutzt wird. Tatsächlich, Die Sprachspezifikation gibt keine Garantie dafür, was wo wohnt. Eine zukünftige Laufzeit kann möglicherweise einige Objekte auf dem Stapel erstellen, wenn sie weiß, dass sie damit durchkommen kann, oder der C # -Compiler kann Code generieren, der den Stapel kaum verwendet. Der nächste Mythos ist normalerweise nur ein Terminologieproblem.

MYTHOS 3: OBJEKTE WERDEN NACH STANDARD IN C # VERWEISEN

Dies ist wahrscheinlich der am weitesten verbreitete Mythos. Auch hier wissen die Personen, die diese Behauptung häufig (wenn auch nicht immer) aufstellen, wie sich C # tatsächlich verhält, aber sie wissen nicht, was „Referenzübergabe“ wirklich bedeutet. Leider ist dies verwirrend für Leute, die wissen, was es bedeutet. Die formale Definition der Referenzübergabe ist relativ kompliziert und umfasst l-Werte und eine ähnliche Informatik-Terminologie. Wichtig ist jedoch, dass die von Ihnen aufgerufene Methode den Wert der Variablen des Aufrufers ändern kann, wenn Sie eine Variable als Referenz übergeben durch Ändern des Parameterwerts. Denken Sie jetzt daran, dass der Wert einer Referenztypvariablen die Referenz und nicht das Objekt selbst ist. Sie können den Inhalt des Objekts ändern, auf das sich ein Parameter bezieht, ohne dass der Parameter selbst als Referenz übergeben wird. Zum Beispiel,

void AppendHello(StringBuilder builder)
{
    builder.Append("hello");
}

Wenn diese Methode aufgerufen wird, wird der Parameterwert (ein Verweis auf einen StringBuilder) als Wert übergeben. Wenn Sie den Wert der Builder-Variablen innerhalb der Methode ändern würden - beispielsweise mit der Anweisung builder = null; -, würde diese Änderung vom Aufrufer entgegen dem Mythos nicht gesehen. Es ist interessant festzustellen, dass nicht nur das "Referenz" -Bit des Mythos ungenau ist, sondern auch das "Objekte werden übergeben" -Bit. Objekte selbst werden weder als Referenz noch als Wert übergeben. Wenn es sich um einen Referenztyp handelt, wird entweder die Variable als Referenz übergeben oder der Wert des Arguments (die Referenz) wird als Wert übergeben. Abgesehen von allem anderen beantwortet dies die Frage, was passiert, wenn null als By-Value-Argument verwendet wird - wenn Objekte herumgereicht würden, würde dies zu Problemen führen, da kein Objekt übergeben werden würde! Stattdessen, Die Nullreferenz wird wie jede andere Referenz als Wert übergeben. Wenn diese kurze Erklärung Sie verwirrt hat, möchten Sie vielleicht meinen Artikel „Parameterübergabe in C #“ lesen (http://mng.bz/otVt ), was viel detaillierter geht. Diese Mythen sind nicht die einzigen. Boxen und Unboxen kommen für ihren fairen Anteil an Missverständnissen ins Spiel, die ich als nächstes zu klären versuchen werde.

Referenz: C # in Depth 3rd Edition von Jon Skeet


1
Sehr gut, wenn Sie richtig liegen. Auch sehr gut, um eine Referenz hinzuzufügen.
NoChance

5

Ich denke, eine gute erste Annäherung ist "nie".

Ich denke, eine gute zweite Annäherung ist "nie".

Wenn Sie verzweifelt nach Perf sind, ziehen Sie sie in Betracht, aber messen Sie dann immer.


24
Ich würde dieser Antwort nicht zustimmen. Strukturen haben in vielen Szenarien eine legitime Verwendung. Hier ist ein Beispiel: Das Zusammenstellen von Daten über Kreuzprozesse auf atomare Weise.
Franci Penov

25
Sie sollten Ihren Beitrag bearbeiten und Ihre Punkte näher erläutern - Sie haben Ihre Meinung abgegeben, aber Sie sollten ihn mit der Begründung belegen, warum Sie diese Meinung vertreten.
Erik Forbes

4
Ich denke, sie benötigen ein Äquivalent der Totin 'Chip-Karte ( en.wikipedia.org/wiki/Totin%27_Chip ) für die Verwendung von Strukturen. Ernsthaft.
Greg

4
Wie postet eine 87,5K-Person eine solche Antwort? Hat er es getan, als er ein Kind war?
Rohit Vipin Mathews

3
@Rohit - es war vor sechs Jahren; Die Standortstandards waren damals sehr unterschiedlich. Dies ist immer noch eine schlechte Antwort, aber Sie haben Recht.
Andrew Arnold

5

Ich zu tun hatte nur mit Windows Communication Foundation [WCF] Rohr Benannte und ich habe bemerkt , dass es keinen Sinn macht Structs zu verwenden , um , dass der Austausch von Daten , um sicherzustellen , Werttyp anstelle von Referenztyp .


1
Dies ist der beste Hinweis von allen, IMHO.
Ivan

4

Die C # -Struktur ist eine einfache Alternative zu einer Klasse. Es kann fast das Gleiche wie eine Klasse tun, aber es ist weniger "teuer", eine Struktur anstelle einer Klasse zu verwenden. Der Grund dafür ist ein bisschen technisch, aber um es zusammenzufassen, werden neue Instanzen einer Klasse auf dem Heap platziert, wo neu instanziierte Strukturen auf dem Stapel platziert werden. Außerdem haben Sie es nicht mit Verweisen auf Strukturen zu tun, wie mit Klassen, sondern Sie arbeiten direkt mit der Strukturinstanz. Dies bedeutet auch, dass wenn Sie eine Struktur an eine Funktion übergeben, diese nach Wert und nicht als Referenz erfolgt. Mehr dazu im Kapitel über Funktionsparameter.

Sie sollten also Strukturen verwenden, wenn Sie einfachere Datenstrukturen darstellen möchten, und insbesondere, wenn Sie wissen, dass Sie viele davon instanziieren werden. Es gibt viele Beispiele im .NET Framework, in denen Microsoft Strukturen anstelle von Klassen verwendet hat, z. B. die Struktur Point, Rectangle und Color.


3

Struct kann verwendet werden, um die Garbage Collection-Leistung zu verbessern. Während Sie sich normalerweise nicht um die GC-Leistung kümmern müssen, gibt es Szenarien, in denen dies ein Killer sein kann. Wie große Caches in Anwendungen mit geringer Latenz. In diesem Beitrag finden Sie ein Beispiel:

http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/


3

Struktur- oder Werttypen können in folgenden Szenarien verwendet werden:

  1. Wenn Sie verhindern möchten, dass das Objekt von der Garbage Collection erfasst wird.
  2. Wenn es sich um einen einfachen Typ handelt und keine Mitgliedsfunktion die Instanzfelder ändert
  3. Wenn es nicht erforderlich ist, von anderen Typen abzuleiten oder von anderen Typen abgeleitet zu werden.

Weitere Informationen zu den Werttypen und Werttypen finden Sie hier unter diesem Link


3

Verwenden Sie kurz struct, wenn:

1- Ihre Objekteigenschaften / Felder müssen nicht geändert werden. Ich meine, Sie möchten ihnen nur einen Anfangswert geben und sie dann lesen.

2- Eigenschaften und Felder in Ihrem Objekt sind vom Werttyp und nicht so groß.

In diesem Fall können Sie Strukturen für eine bessere Leistung und eine optimierte Speicherzuordnung nutzen, da sie nur Stapel anstelle von Stapeln und Heaps (in Klassen) verwenden.


2

Ich benutze selten eine Struktur für Dinge. Aber das bin nur ich. Es hängt davon ab, ob das Objekt nullwertfähig sein soll oder nicht.

Wie in anderen Antworten angegeben, verwende ich Klassen für reale Objekte. Ich habe auch die Einstellung, dass Strukturen zum Speichern kleiner Datenmengen verwendet werden.


-11

Strukturen ähneln in den meisten Fällen Klassen / Objekten. Struktur kann Funktionen und Elemente enthalten und vererbt werden. Strukturen werden jedoch in C # nur zum Speichern von Daten verwendet . Strukturen benötigen weniger RAM als Klassen und sind für den Garbage Collector einfacher zu sammeln . Wenn Sie jedoch Funktionen in Ihrer Struktur verwenden, verwendet der Compiler diese Struktur tatsächlich sehr ähnlich als Klasse / Objekt. Wenn Sie also etwas mit Funktionen möchten , verwenden Sie Klasse / Objekt .


2
Strukturen können NICHT vererbt werden, siehe msdn.microsoft.com/en-us/library/0taef578.aspx
HimBromBeere
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.