Frage Wann man struct benutzt?


Wann sollten Sie in C # struct und nicht class verwenden? Mein konzeptionelles Modell ist, dass Strukturen in Zeiten verwendet werden, in denen das Element ist lediglich eine Sammlung von Werttypen. Ein Weg, sie logisch zu einem Ganzen zusammen zu halten.

Ich bin auf diese Regeln gestoßen Hier:

  • Eine Struktur sollte eine einzige darstellen Wert.
  • Eine Struktur sollte einen Speicher haben Fußabdruck weniger als 16 Bytes.
  • Eine Struktur sollte nicht nach geändert werden Schaffung.

Funktionieren diese Regeln? Was bedeutet eine Struktur semantisch?


1201
2018-02-06 17:37


Ursprung


Antworten:


Die Quelle, auf die das OP verweist, hat eine gewisse Glaubwürdigkeit ... aber wie steht es mit Microsoft - wie steht es mit der Verwendung von Strukturen? Ich habe etwas extra gesucht von Microsoft lernenund hier ist, was ich gefunden habe:

Erwägen Sie, eine Struktur anstelle einer Klasse zu definieren, wenn Instanzen von   Typ ist klein und häufig kurzlebig oder sind in der Regel eingebettet   andere Objekte.

Definieren Sie keine Struktur, wenn der Typ nicht alle folgenden Merkmale aufweist: 

  1. Es stellt logisch einen einzelnen Wert dar, der primitiven Typen (Integer, Double usw.) ähnelt.
  2. Es hat eine Instanzgröße von weniger 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 sein würde. Wenn Sie eine Struktur einpacken, überdenken Sie Ihre Architektur.

Lassen Sie uns sehen, warum Microsoft diese Strukturen verwenden würde:

  1. Jede Struktur, Entry und Enumerator, repräsentieren einzelne Werte.
  2. Geschwindigkeit
  3. Entry wird nie als Parameter außerhalb der Dictionary-Klasse übergeben. Weitere Untersuchungen zeigen, dass Dictionary verwendet, um die Implementierung von IEnumerable zu erfüllen Enumerator struct, die jedesmal kopiert wird, wenn ein Enumerator angefordert wird ... macht Sinn.
  4. Intern zur Dictionary-Klasse. Enumerator ist öffentlich, weil das Dictionary aufzählbar ist und die IEnumerator-Schnittstellenimplementierung in gleicher Weise zugänglich sein muss - z. IEnumerator Getter.

Aktualisieren - Stellen Sie außerdem fest, 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. Intern zur Dictionary-Klasse Enumerator ist immer noch ein Werttyp. Sobald jedoch eine Methode aufruft GetEnumerator(), ein Referenztyp IEnumerator ist zurück gekommen.

Was wir hier nicht sehen, ist jeglicher Versuch oder Beweis für die Notwendigkeit, Strukturen unwandelbar 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. Entry hat eine unbestimmte Lebenszeit (von Add()bis Remove(), Clear()oder Müllsammlung);

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

Unabhängig von Hash-Schlüsseln sind Wörterbücher teilweise schnell, da das Instancieren einer Struktur schneller ist als ein Referenztyp. Hier habe ich eine Dictionary<int, int> Diese speichert 300.000 zufällige Ganzzahlen mit sequentiell inkrementierten Schlüsseln.

Kapazität: 312874
  MemSize: 2660827 Bytes
  Abgeschlossen Größe ändern: 5ms
  Gesamtzeit zum Ausfüllen: 889ms

Kapazität: Anzahl der verfügbaren Elemente, bevor das interne Array in der Größe geändert werden muss.

MemSize: festgestellt, indem das Wörterbuch in einen MemoryStream serialisiert und eine Byte-Länge erhalten wird (genau genug für unsere Zwecke).

Abgeschlossen Größe ändern: Die Zeit, die benötigt wird, um das interne Array von 150862 Elementen auf 312874 Elemente zu skalieren. Wenn Sie feststellen, dass jedes Element sequenziell über kopiert wird Array.CopyTo()Das ist nicht zu schäbig.

Gesamtzeit zu füllen: zugegebenermaßen verzerrt wegen Abholzung und einer OnResize Ereignis, das ich der Quelle hinzugefügt habe; Dennoch ist es immer noch beeindruckend, 300k ganze Zahlen zu füllen, während die Größe während der Operation 15-mal geändert wird. Nur aus Neugier, was wäre die gesamte Zeit zu füllen, wenn ich die Kapazität bereits kannte? 13ms 

So, jetzt, was wenn Entry war eine Klasse? Würden diese Zeiten oder Metriken wirklich so unterschiedlich sein?

Kapazität: 312874
  MemSize: 2660827 Bytes
  Abgeschlossen Größe ändern: 26ms
  Gesamtzeit zum Ausfüllen: 964ms

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

Was passiert ist, weil Entry ist eine Struktur, erfordert keine Initialisierung wie ein Referenztyp. Dies ist sowohl die Schönheit als auch der Fluch des Werttyps. Um zu verwenden Entry Als Referenztyp musste ich 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 von Entry als Referenztyp finden Sie unter MSDN: Strukturentwurf. Zusamenfassend:

Stellen Sie keinen Standardkonstruktor für eine Struktur bereit.

Wenn eine Struktur einen Standardkonstruktor definiert, wenn Arrays der   Struktur erstellt werden, die Common Language Runtime automatisch   Führt den Standardkonstruktor für jedes Array-Element aus.

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

Es ist eigentlich ziemlich einfach, und wir werden ausleihen Asimovs Drei Gesetze der Robotik:

  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 muss Regel 1 erfüllen

...Was nehmen wir davon weg?Kurz gesagt, sei verantwortlich mit der Verwendung von Werttypen. Sie sind schnell und effizient, haben jedoch die Fähigkeit, viele unerwartete Verhaltensweisen zu verursachen, wenn sie nicht richtig gepflegt werden (d. H. Unbeabsichtigte Kopien).


541
2017-08-07 13:44



Wenn Sie keinen Polymorphismus benötigen, Semantik für Werte benötigen und die Heap-Zuordnung und den damit verbundenen Garbage Collection-Overhead vermeiden möchten. Der Nachteil ist jedoch, dass Strukturen (beliebig groß) teurer sind als Klassenreferenzen (normalerweise ein Maschinenwort), sodass Klassen in der Praxis schneller sein könnten.


142
2018-02-06 17:40



Ich stimme den im ursprünglichen Beitrag angegebenen Regeln nicht zu. Hier sind meine Regeln:

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

2) Sie benötigen sie im Code, der strukturierte Daten zu / von C / C ++ übergibt

3) Verwenden Sie keine Strukturen, außer Sie brauchen sie:

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

133
2018-02-28 16:33



Verwenden Sie eine Struktur, wenn Sie Wert-Semantik im Gegensatz zu Referenz-Semantik wollen.

Bearbeiten

Nicht sicher, warum Leute dies ablehnen, aber das ist ein gültiger Punkt, und wurde gemacht, bevor das Op seine Frage geklärt hat, und es ist der grundlegendste Grundgrund für eine Struktur.

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


82
2018-02-06 17:40



Zusätzlich zu der Antwort "es ist ein Wert" gibt es ein spezifisches Szenario für die Verwendung von Strukturen, wenn Sie kennt dass Sie eine Reihe von Daten haben, die Garbage Collection-Probleme verursacht, und Sie viele Objekte haben. Zum Beispiel eine große Liste / ein Array von Personeninstanzen. Die natürliche Metapher ist hier eine Klasse, aber wenn Sie eine große Anzahl langlebiger Personen-Instanzen haben, können sie GEN-2 verstopfen und GC-Staus verursachen. Wenn das Szenario dies rechtfertigt, besteht ein möglicher Ansatz darin, ein Array (keine Liste) von Person zu verwenden Strukturen, d.h. Person[]. Jetzt, anstatt Millionen von Objekten in GEN-2 zu haben, haben Sie einen einzigen Chunk auf der LOH (ich nehme hier keine Strings usw. an - d. H. Einen reinen Wert ohne Referenzen). Dies hat sehr geringe Auswirkungen auf die GC.

Die Arbeit mit diesen Daten ist umständlich, da die Daten wahrscheinlich für eine Struktur überdimensioniert 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 in-place (im Gegensatz zu einem Listenindexer, der kopiert). Das bedeutet viel Arbeit mit Indizes:

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

Beachten Sie, dass das Beibehalten der Werte selbst unveränderlich hier hilft. 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 in-Place - wir haben den Wert nicht kopiert.

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


54
2017-10-22 12:14



Von dem C # Sprachspezifikation:

1.7 Strukturen 

Ähnlich wie Klassen sind Strukturen Datenstrukturen, die Datenelemente und Funktionselemente enthalten können, aber im Gegensatz zu Klassen sind Strukturen   Werttypen und erfordern keine Heap-Zuweisung. Eine Variable einer Struktur   type speichert die Daten der Struktur direkt, während eine Variable von a   Klassentyp speichert einen Verweis auf ein dynamisch zugewiesenes Objekt.   Struktypen unterstützen keine benutzerdefinierte Vererbung und alle Strukturen   Typen erben implizit vom Typ Objekt.

Strukturen sind besonders nützlich für kleine Datenstrukturen, die haben   Wert Semantik. Komplexe Zahlen, Punkte in einem Koordinatensystem oder   Schlüssel-Wert-Paare in einem Wörterbuch sind alles gute Beispiele für Strukturen. Das   Verwendung von Strukturen anstelle von Klassen für kleine Datenstrukturen kann erfolgen   ein großer Unterschied in der Anzahl der Speicherzuweisungen einer Anwendung   führt durch. Beispielsweise erstellt und initialisiert das folgende Programm   ein Array von 100 Punkten. Mit Point als Klasse implementiert, 101   Einzelne Objekte werden instanziiert - eins für das Array und eins für jedes   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.

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

Mit Klassen ist es möglich, dass zwei Variablen auf dasselbe Objekt verweisen und somit Operationen für eine Variable das Objekt beeinflussen können, auf das die andere Variable verweist. Bei Strukturen haben die Variablen jeweils ihre eigene Kopie der Daten und es ist nicht möglich, dass sich die Operationen auf die andere auswirken. Zum Beispiel hängt die Ausgabe, die durch das folgende Codefragment erzeugt wird, 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 dasselbe Objekt referenzieren. Wenn Point eine Struktur ist, ist die Ausgabe 10, da die Zuweisung von a zu b eine Kopie des Werts erstellt und diese Kopie von der nachfolgenden Zuweisung zu a.x nicht beeinflusst wird.

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


36
2017-09-17 15:42



Strukturen sind gut für die atomare Darstellung von Daten, wobei die genannten Daten mehrfach durch den Code kopiert werden können. Das Klonen eines Objekts ist im Allgemeinen teurer als das Kopieren einer Struktur, da es die Zuweisung des Arbeitsspeichers, das Ausführen des Konstruktors und das Aufheben der Zuordnung / Garbage-Collection mit sich bringt.


31
2018-02-06 17:58



Hier ist eine Grundregel.

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

  • Wenn ein Mitgliedsfeld ein Referenztyp ist, erstellen Sie ein Klasse. Dies liegt daran, dass das Referenztyp-Feld die Heap-Zuweisung ohnehin benötigt.

Exmaples

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
}

24
2018-01-22 10:17