1628 Stimmen

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

Wann sollten Sie in C# struct und nicht class verwenden? Mein konzeptionelles Modell besagt, dass structs verwendet werden, wenn das Element nur eine Sammlung von Wertetypen ist. Eine Möglichkeit, sie alle logisch zu einem zusammenhängenden Ganzen zusammenzufassen.

Ich bin auf diese Regeln hier gestoßen:

  • Ein struct sollte einen einzigen Wert repräsentieren.
  • Ein struct sollte einen Speicherbedarf von weniger als 16 Bytes haben.
  • Ein struct sollte nach der Erstellung nicht verändert werden.

Funktionieren diese Regeln? Was bedeutet ein struct semantisch?

0 Stimmen

Ich kann nur dem ersten Punkt zustimmen, Strukturen werden zum Beispiel sehr häufig in der Spieleprogrammierung verwendet.

305 Stimmen

System.Drawing.Rectangle verletzt alle drei dieser Regeln.

3 Stimmen

Ja, nun gut, zumindest Teile davon. Ich weiß, dass es für Teile von Spielen verwendet wird, wie NWN2 World Creator. C ist in der Regel immer noch das Kernelement (Engine). XNA Game Studio, Google es :)

684voto

IAbstract Punkte 19409

Die von OP referenzierte Quelle hat eine gewisse Glaubwürdigkeit ... aber wie steht es mit Microsoft - was ist die Meinung zur Verwendung von Strukturen? Ich habe mir einige zusätzliche Informationen von Microsoft gesucht, und hier ist, was ich gefunden habe:

Erwägen Sie das Definieren einer Struktur anstelle einer Klasse, wenn Instanzen des Typs klein und häufig kurzlebig sind oder häufig in anderen Objekten eingebettet sind.

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

  1. Er stellt logisch einen einzigen Wert dar, ähnlich wie primitive Typen (Integer, Double usw.).
  2. Die Instanzgröße ist kleiner als 16 Bytes.
  3. Es ist unveränderlich.
  4. Es muss nicht häufig geboxt werden.

Microsoft verletzt diese Regeln konsequent

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

[StructLayout(LayoutKind.Sequential)]  // Standard für Strukturen
private struct Eintrag  //
{
    //  Code anzeigen auf *Referenzquelle
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator : 
    IEnumerator>, IDisposable, 
    IDictionaryEnumerator, IEnumerator
{
    //  Code anzeigen auf *Referenzquelle
}

*Referenzquelle

Die Quelle 'JonnyCantCode.com' hat 3 von 4 - ziemlich verzeihlich, da #4 wahrscheinlich kein Problem wäre. Wenn Sie sich dabei erwischen, wie Sie eine Struktur boxen, überdenken Sie Ihre Architektur.

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

  1. Jede Struktur, Eintrag und Enumerator, repräsentiert einzelne Werte.
  2. Geschwindigkeit
  3. Eintrag wird nie als Parameter außerhalb der Dictionary-Klasse übergeben. Weitere Untersuchungen zeigen, dass Dictionary zur Erfüllung der Implementierung von IEnumerable die Enumerator-Struktur verwendet, die jedes Mal kopiert wird, wenn ein Enumerator angefordert wird ... macht Sinn.
  4. Intern zur Dictionary-Klasse. Enumerator ist öffentlich, weil Dictionary auflistbar ist und eine gleichwertige Zugänglichkeit zur IEnumerator-Schnittstellenimplementierung haben muss - z.B. IEnumerator-Getter.

Aktualisierung - Darüber hinaus erkennen Sie, dass eine Struktur, wenn Enumerator eine Schnittstelle implementiert, und als diese implementierte Typus umgewandelt wird, die Struktur zu einem Referenztypus wird und auf den Heap verschoben wird. Intern zur Dictionary-Klasse ist Enumerator jedoch immer noch ein Werttypus. Sobald jedoch eine Methode GetEnumerator() aufruft, wird ein Referenztypus IEnumerator zurückgegeben.

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

  1. In den obigen Strukturen ist nichts als schreibgeschützt deklariert - nicht unveränderlich
  2. Die Größe dieser Struktur könnte weit über 16 Bytes liegen
  3. Eintrag hat eine unbestimmte Lebensdauer (von Hinzufügen() über Entfernen(), Leeren() oder Garbage Collection);

Und ... 4. Beide Strukturen speichern TKey und TValue, von denen wir alle wissen, dass sie durchaus in der Lage sind, Referenztypen zu sein (zusätzliche Bonusinformationen)

Ungeachtet der gehashten Schlüssel sind Wörterbücher schnell, unter anderem weil die Instanziierung einer Struktur schneller ist als die eines Referenztypus. Hier habe ich ein Dictionary, welches 300.000 zufällige Ganzzahlen mit sequenziell inkrementierten Schlüsseln speichert.

Kapazität: 312874
MemGröße: 2660827 Bytes
Abgeschlossene Größenänderung: 5ms
Gesamtzeit zum Füllen: 889ms

Kapazität: Anzahl der Elemente, die verfügbar sind, bevor das interne Array vergrößert werden muss.

MemGröße: Bestimmt durch Serialisieren des Wörterbuchs in ein MemoryStream und Ermitteln einer Byte-Länge (genau genug für unsere Zwecke).

Abgeschlossene Größenänderung: Die Zeit, die benötigt wird, um das interne Array von 150862 Elementen auf 312874 Elemente zu vergrößern. Wenn man bedenkt, dass jedes Element sequenziell über Array.CopyTo() kopiert wird, ist das gar nicht schlecht.

Gesamtzeit zum Füllen: Zugegebenermaßen durch Protokollierung und ein OnResize-Ereignis in der Quelle verzerrt; dennoch beeindruckend, 300.000 Ganzzahlen zu füllen, während 15-mal während des Vorgangs neu dimensioniert wird. Nur aus Neugier, wie lange würde die Gesamtzeit zum Füllen sein, wenn ich die Kapazität bereits kennen würde? 13ms

Also, was wäre, wenn Eintrag eine Klasse wäre? Würden sich diese Zeiten oder Metriken wirklich so sehr unterscheiden?

Kapazität: 312874
MemGröße: 2660827 Bytes
Abgeschlossene Größenänderung: 26ms
Gesamtzeit zum Füllen: 964ms

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

Was passiert ist, dass Eintrag eine Struktur ist und keine Initialisierung wie ein Referenztypus erfordert. Das ist sowohl die Schönheit als auch der Fluch des Werttypus. Um Eintrag als Referenztypus zu verwenden, musste ich den folgenden Code einfügen:

/*
 *  Hinzugefügt, um die Initialisierung der Eintragselemente zu erfüllen -
 *  hier wird die zusätzliche Zeit beim Neudimensionieren des Eintrag-Arrays verbracht
 * **/
for (int i = 0 ; i < prime ; i++)
{
    destinationArray[i] = new Eintrag( );
}
/*  *********************************************** */  

Der Grund, warum ich jedes Arrayelement von Eintrag als Referenztypus initialisieren musste, finden Sie unter MSDN: Strukturdesign. Kurz gesagt:

Stellen Sie keinen Standardkonstruktor für eine Struktur bereit.

Wenn eine Struktur einen Standardkonstruktor definiert, führt das Common Language Runtime automatisch den Standardkonstruktor für jedes Arrayelement aus, wenn Arrays der Struktur erstellt werden.

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

Es ist eigentlich ziemlich einfach und wir werden uns von Asimovs Drei Gesetzen der Robotik inspirieren lassen:

  1. Die Struktur muss sicher zu verwenden sein
  2. Die Struktur muss ihre Funktion effizient erfüllen, 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 daraus: Kurz gesagt, verantwortungsbewusst mit der Verwendung von Werttypen umgehen. Sie sind schnell und effizient, haben aber die Möglichkeit, viele unerwartete Verhaltensweisen zu verursachen, wenn sie nicht ordnungsgemäß gewartet werden (z.B. unbeabsichtigte Kopien).

0 Stimmen

Ich glaube nicht, dass die Laufzeit jemals einen Strukturkonstruktor aufruft, wenn sie ein Array von Strukturen erstellt. Der Grund, warum die meisten Sprachen es verbieten, dass Strukturen einen Standardkonstruktor haben, ist nicht, weil das Aufrufen von ihnen die Arraykonstruktion ineffizient machen würde, sondern weil es verwirrend wäre, Strukturkonstruktoren in einigen Fällen aufzurufen, aber nicht in anderen.

11 Stimmen

Was Microsofts Richtlinien betrifft, scheint die Regel zur Unveränderlichkeit darauf ausgelegt zu sein, den Gebrauch von Werttypen in einer Weise zu entmutigen, dass sich ihr Verhalten von dem von Referenztypen unterscheiden würde, ungeachtet der Tatsache, dass stückweise veränderbare Wertsemantiken nützlich sein können. Wenn es einfacher wäre, mit einem Typ zu arbeiten, der stückweise veränderbar ist, und wenn die Speicherorte des Typs logisch voneinander getrennt sein sollten, sollte der Typ eine "veränderbare" Struktur sein.

3 Stimmen

Die Tatsache, dass viele der Typen von Microsoft gegen diese Regeln verstoßen, stellt kein Problem mit diesen Typen dar, sondern deutet vielmehr darauf hin, dass die Regeln nicht auf alle Strukturtypen angewendet werden sollten. Wenn eine Struktur eine einzelne Entität darstellt [wie bei Decimal oder DateTime], dann sollte sie durch eine Klasse ersetzt werden, wenn sie sich nicht an die anderen drei Regeln hält. Wenn eine Struktur eine feste Sammlung von Variablen enthält, von denen jede einen beliebigen gültigen Wert für ihren Typ enthalten kann [z. B. Rectangle], dann sollte sie sich an andere Regeln halten, von denen einige im Gegensatz zu denen für "Einzelwert" Strukturen stehen.

194voto

dsimcha Punkte 65784

Immer wenn du:

  1. keine Polymorphie benötigst,
  2. Wertsemantik möchtest, und
  3. Heap-Allokation und den damit verbundenen Garbage-Collection-Overhead vermeiden möchtest.

Die Einschränkung ist jedoch, dass Strukturen (beliebig groß) teurer sind, um weitergereicht zu werden als Klassenverweise (in der Regel ein Maschinenwort), sodass Klassen in der Praxis möglicherweise schneller sein könnten.

1 Stimmen

Das ist nur ein "Haken". Sollte auch das "Hochheben" von Werttypen und Fällen wie (Guid)null (es ist in Ordnung, null zu einem Referenztyp zu casten) in Betracht ziehen, unter anderem.

1 Stimmen

Ist in C++ teurer als in C/C++? In C++ wird empfohlen, Objekte per Wert zu übergeben.

0 Stimmen

@IonTodirel War das nicht aus Gründen der Speichersicherheit, statt der Leistung? Es ist immer ein Kompromiss, aber das Übergeben von 32 B über den Stapel ist immer langsamer als das Übergeben einer 4 B-Referenz über ein Register. Allerdings beachten Sie auch, dass die Verwendung von "Wert / Referenz" in C# und C++ etwas anders ist - wenn Sie eine Referenz auf ein Objekt übergeben, übergeben Sie immer noch nach Wert, auch wenn Sie eine Referenz übergeben (Sie übergeben den Wert der Referenz, nicht eine Referenz auf die Referenz, im Grunde genommen). Es ist keine Wert-semantik, aber es ist technisch gesehen "pass-by-value".

177voto

ILoveFortran Punkte 3281

Ich stimme nicht mit den Regeln überein, die im ursprünglichen Beitrag angegeben sind. Hier sind meine Regeln:

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

  2. Sie benötigen sie im Code, der strukturierte Daten nach/von C/C++ überträgt

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

    • Sie verhalten sich anders als "normale Objekte" (Referenztypen) bei der Zuweisung und beim Übergeben als Argumente, was zu unerwartetem Verhalten führen kann; dies ist besonders gefährlich, wenn die Person, die den Code betrachtet, nicht weiß, dass sie es mit einer Struktur zu tun haben.
    • Sie können nicht geerbt werden.
    • Das Übergeben von Strukturen als Argumente ist teurer als bei Klassen.

4 Stimmen

+1 Ja, ich stimme vollständig zu (#1) (das ist ein riesiger Vorteil bei der Behandlung von Dingen wie Bildern usw.) und dafür, dass sie anders sind als "normale Objekte" und es keinen Weg gibt, dies zu wissen außer durch vorhandenes Wissen oder Untersuchung des Typs selbst. Außerdem kannst du einen Nullwert nicht in einen Strukturtyp umwandeln :-) Das ist tatsächlich ein Fall, in dem ich fast wünschte, es gäbe etwas wie 'ungarisch' für nicht-Core-Werttypen oder ein obligatorisches 'struct'-Schlüsselwort bei der Variablendeklaration.

0 Stimmen

@pst: Es ist wahr, dass man wissen muss, dass etwas ein struct ist, um zu wissen, wie es sich verhalten wird. Aber wenn etwas ein struct mit freiliegenden Feldern ist, ist das alles, was man wissen muss. Wenn ein Objekt eine Eigenschaft eines freiliegenden Feld-struct-Typs freigibt und wenn Code dieses struct in eine Variable liest und ändert, kann man sicher vorhersagen, dass eine solche Aktion das Objekt, dessen Eigenschaft gelesen wurde, nicht beeinflussen wird, es sei denn, das struct wird zurückgeschrieben. Im Gegensatz dazu, wenn die Eigenschaft eines veränderlichen Klassentyps wäre, das Lesen und Ändern könnte das zugrunde liegende Objekt wie erwartet aktualisieren, aber...

0 Stimmen

... es könnte auch passieren, dass sich nichts ändert, oder dass sich Objekte ändern oder beschädigt werden, die man nicht ändern wollte. Es erscheint klarer zu haben, Code, dessen Semantik besagt "ändere diese Variable so wie du willst; Änderungen werden erst dann etwas bewirken, wenn du sie ausdrücklich an einem anderen Ort speicherst", als Code zu haben, der sagt "Du bekommst eine Referenz zu einem Objekt, das möglicherweise mit einer beliebigen Anzahl anderer Referenzen geteilt wird oder überhaupt nicht geteilt wird; du musst herausfinden, wer noch Referenzen zu diesem Objekt haben könnte, um zu wissen, was passiert, wenn du es änderst."

98voto

JoshBerke Punkte 64214

Verwenden Sie eine Struktur, wenn Sie Wertsemantik anstelle von Referenzsemantik möchten.

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

25 Stimmen

Jeder weiß das. Scheint, als ob er mehr als nur eine "struct ist ein Werttyp" Antwort sucht.

27 Stimmen

Es handelt sich um den einfachsten Fall und sollte für jeden klar sein, der diesen Beitrag liest und das nicht weiß.

5 Stimmen

Nicht, dass diese Antwort nicht richtig ist; offensichtlich ist sie das. Das ist nicht wirklich der Punkt.

67voto

Marc Gravell Punkte 970173

Neben der Antwort "es handelt sich um einen Wert" ist ein spezifisches Szenario für die Verwendung von Strukturen, wenn Sie wissen, dass Sie einen Datensatz haben, der Probleme mit der Müllsammlung verursacht, und Sie eine Vielzahl von Objekten haben. Zum Beispiel eine große Liste/Array von Person-Instanzen. Die natürliche Metapher hier ist eine Klasse, aber wenn Sie eine große Anzahl von langfristigen Person-Instanzen haben, können sie GEN-2 verstopfen und GC-Verzögerungen verursachen. Wenn das Szenario es rechtfertigt, ist ein möglicher Ansatz hier die Verwendung eines Arrays (keine Liste) von Person-strukturen, d.h. Person[]. Jetzt haben Sie nicht Millionen von Objekten in GEN-2, sondern einen einzigen Block im LOH (ich gehe davon aus, dass keine Zeichenfolgen usw. vorhanden sind - d.h. ein reiner Wert ohne Verweise). Dies hat sehr geringe Auswirkungen auf die Müllsammlung.

Das Arbeiten mit diesen Daten ist umständlich, da die Daten wahrscheinlich zu groß für eine Struktur sind und Sie nicht wollen, dass wertvolle Werte ständig kopiert werden. Der direkte Zugriff in einem Array kopiert die Struktur jedoch nicht - sie bleibt an Ort und Stelle (im Gegensatz zu einem Listenindexer, der kopiert). Dies bedeutet viel Arbeit mit Indizes:

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

Beachten Sie, dass das Halten der Werte selbst unveränderlich hier hilfreich sein wird. Für komplexere Logik verwenden Sie eine Methode mit einem Verweisparameter:

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

Auch hier erfolgt dies vor Ort - der Wert wird nicht kopiert.

In sehr spezifischen Szenarien kann diese Taktik sehr erfolgreich sein; es handelt sich jedoch um ein recht fortgeschrittenes Szenario, das nur versucht werden sollte, wenn Sie wissen, was Sie tun und warum. Die Standardeinstellung hier wäre eine Klasse.

0 Stimmen

+1 Interessante Antwort. Würden Sie bereit sein, einige reale Anekdoten darüber zu teilen, wie ein solcher Ansatz verwendet wurde?

0 Stimmen

@Jordao ist auf seinem Handy, aber suche bei Google nach: +gravell +"Angriff durch GC"

2 Stimmen

Vielen Dank. Ich habe es hier gefunden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X