Wann verwenden Sie eine Struktur anstelle einer Klasse? [geschlossen]


174

Was sind Ihre Faustregeln für die Verwendung von Strukturen im Vergleich zu Klassen? Ich denke an die C # -Definition dieser Begriffe, aber wenn Ihre Sprache ähnliche Konzepte hat, würde ich gerne auch Ihre Meinung hören.

Ich neige dazu, Klassen für fast alles zu verwenden, und benutze Strukturen nur, wenn etwas sehr simpel ist und ein Werttyp sein sollte, wie z. B. eine PhoneNumber oder ähnliches. Dies scheint jedoch eine relativ geringe Verwendung zu sein, und ich hoffe, dass es interessantere Anwendungsfälle gibt.



7
@Frustrated Eine Frage kann nicht als Duplikat von etwas auf einer anderen Site markiert werden, obwohl dies mit Sicherheit zusammenhängt.
Adam Lear

@Anna Lear: Ich weiß, ich habe für die Migration gestimmt, um nicht als Duplikat zu schließen. Nach der Migration kann es ordnungsgemäß als Duplikat geschlossen werden .
FrustratedWithFormsDesigner

5
@Frustriert Ich glaube nicht, dass es migriert werden muss.
Adam Lear

15
Dies scheint eine Frage zu sein, die eher für P.SE als für SO geeignet ist. Deshalb habe ich es hier gefragt.
RationalGeek

Antworten:


169

Als allgemeine Regel gilt, dass Strukturen kleine, einfache (einstufige) Sammlungen verwandter Eigenschaften sein sollten, die nach ihrer Erstellung unveränderlich sind. Verwenden Sie für alles andere eine Klasse.

C # hat den Vorteil, dass Strukturen und Klassen außer dem Schlüsselwort defining keine expliziten Unterschiede in der Deklaration aufweisen. Wenn Sie also das Gefühl haben, eine Struktur zu einer Klasse "upgraden" oder umgekehrt eine Klasse zu einer Struktur "downgraden" zu müssen, müssen Sie meistens das Schlüsselwort ändern (es gibt ein paar andere Fallstricke; Strukturen können nicht abgeleitet werden) aus einer anderen Klasse oder einem anderen Strukturtyp und sie können keinen parameterlosen Standardkonstruktor explizit definieren).

Ich sage "meistens", weil das Wichtigste, was man über Strukturen wissen muss, ist, dass es anderthalb Mal schmerzen kann, wenn man sie wie Klassen (Referenztypen) behandelt, weil sie Werttypen sind. Insbesondere das Ändern der Eigenschaften einer Struktur kann zu unerwartetem Verhalten führen.

Angenommen, Sie haben eine Klasse SimpleClass mit zwei Eigenschaften, A und B. Sie instanziieren eine Kopie dieser Klasse, initialisieren A und B und übergeben die Instanz dann an eine andere Methode. Diese Methode ändert A und B weiter. Zurück in der aufrufenden Funktion (derjenigen, die die Instanz erstellt hat) haben die A und B Ihrer Instanz die Werte, die ihnen von der aufgerufenen Methode zugewiesen wurden.

Jetzt machen Sie es eine Struktur. Die Eigenschaften sind noch veränderbar. Sie führen dieselben Operationen mit derselben Syntax wie zuvor aus, aber jetzt befinden sich die neuen Werte von A und B nach dem Aufruf der Methode nicht in der Instanz. Was ist passiert? Nun, Ihre Klasse ist jetzt eine Struktur, was bedeutet, dass es sich um einen Wertetyp handelt. Wenn Sie einen Werttyp an eine Methode übergeben, wird standardmäßig (ohne out- oder ref-Schlüsselwort) "nach Wert" übergeben. Eine flache Kopie der Instanz wird für die Verwendung durch die Methode erstellt und dann zerstört, wenn die Methode abgeschlossen ist und die ursprüngliche Instanz intakt bleibt.

Dies wird noch verwirrender, wenn Sie einen Referenztyp als Mitglied Ihrer Struktur haben (nicht unzulässig, aber in praktisch allen Fällen äußerst schlechte Praxis); Die Klasse wird nicht geklont (nur der Verweis der Struktur darauf). Änderungen an der Struktur wirken sich nicht auf das ursprüngliche Objekt aus. Änderungen an der Unterklasse der Struktur wirken sich jedoch auf die Instanz aus, die vom aufrufenden Code stammt. Dies kann sehr leicht veränderbare Strukturen in sehr inkonsistente Zustände versetzen, die weit entfernt vom eigentlichen Problem Fehler verursachen können.

Aus diesem Grund fordert praktisch jede Autorität in C #, Ihre Strukturen immer unveränderlich zu machen. Erlauben Sie dem Konsumenten, die Werte der Eigenschaften nur beim Erstellen eines Objekts anzugeben, und bieten Sie niemals die Möglichkeit, die Werte dieser Instanz zu ändern. Readonly-Felder oder Get-Only-Eigenschaften sind die Regel. Wenn der Konsument den Wert ändern möchte, kann er ein neues Objekt basierend auf den Werten des alten Objekts mit den gewünschten Änderungen erstellen oder eine Methode aufrufen, die dasselbe tut. Dies zwingt sie, eine einzelne Instanz Ihrer Struktur als einen begrifflichen "Wert" zu behandeln, der unteilbar und von allen anderen verschieden (aber möglicherweise allen anderen gleichwertig) ist. Wenn sie eine Operation für einen von Ihrem Typ gespeicherten "Wert" ausführen, erhalten sie einen neuen "Wert", der sich von ihrem Anfangswert unterscheidet.

Ein gutes Beispiel finden Sie unter DateTime. Sie können keines der Felder einer DateTime-Instanz direkt zuweisen. Sie müssen entweder eine neue erstellen oder eine Methode für die vorhandene aufrufen, die eine neue Instanz erzeugt. Dies liegt daran, dass Datum und Uhrzeit ein "Wert" sind, wie die Zahl 5, und eine Änderung der Zahl 5 einen neuen Wert ergibt, der nicht 5 ist. Nur weil 5 + 1 = 6 bedeutet, dass 5 jetzt 6 ist weil du 1 hinzugefügt hast. DateTimes funktionieren genauso. 12:00 wird nicht zu 12:01, wenn Sie eine Minute hinzufügen. Stattdessen erhalten Sie einen neuen Wert 12:01, der sich von 12:00 unterscheidet. Wenn dies ein logischer Sachverhalt für Ihren Typ ist (gute konzeptionelle Beispiele, die in .NET nicht integriert sind, sind Money, Distance, Weight und andere Mengen einer Maßeinheit, bei denen Operationen alle Teile des Werts berücksichtigen müssen), dann benutze eine Struktur und gestalte sie entsprechend. Verwenden Sie in den meisten anderen Fällen, in denen die Unterelemente eines Objekts unabhängig voneinander veränderbar sein sollen, eine Klasse.


1
Gute Antwort! Ich möchte darauf hinweisen, dass Sie es in der "Domain Driven Development" -Methode und in einem Buch lesen, das etwas mit Ihrer Meinung zu
tun hat,

1
Nur ein kleines Detail, das es zu erwähnen gilt: Sie können keinen eigenen Standardkonstruktor für eine Struktur definieren. Sie müssen mit dem auskommen, der alles auf seinen Standardwert initialisiert. Normalerweise ist das ziemlich geringfügig, besonders in Klassen, die sich gut für eine Struktur eignen. Dies bedeutet jedoch, dass das Ändern einer Klasse in eine Struktur mehr als nur das Ändern eines Schlüsselworts bedeuten kann.
Laurent Bourgault-Roy

1
@ LaurentBourgault-Roy - Stimmt. Es gibt auch andere Fallstricke; Beispielsweise können Strukturen keine Vererbungshierarchie haben. Sie können Schnittstellen implementieren, können jedoch nur von System.ValueType abgeleitet werden (implizit dadurch, dass sie Strukturen sind).
KeithS

Als JavaScript-Entwickler muss ich zwei Dinge fragen. Wie unterscheiden sich Objektliterale von der typischen Verwendung von Strukturen und warum ist es wichtig, dass Strukturen unveränderlich sind?
Erik Reppen

1
@ErikReppen: Zur Beantwortung Ihrer beiden Fragen: Wenn eine Struktur an eine Funktion übergeben wird, wird sie als Wert übergeben. Bei einer Klasse übergeben Sie einen Verweis auf die Klasse (immer noch nach Wert). Eric Lippert erläutert, warum dies ein Problem ist: blogs.msdn.com/b/ericlippert/archive/2008/05/14/… . Der letzte Absatz von KeithS behandelt auch diese Fragen.
Brian

14

Die Antwort: "Verwenden Sie eine Struktur für reine Datenkonstrukte und eine Klasse für Objekte mit Operationen" ist definitiv falsch. Wenn eine Struktur eine große Anzahl von Eigenschaften enthält, ist eine Klasse fast immer geeigneter. Aus Sicht der Effizienz sagt Microsoft oft, wenn Ihr Typ größer als 16 Byte ist, sollte er eine Klasse sein.

https://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes


1
Absolut. Ich würde mich steigern, wenn ich könnte. Ich verstehe nicht, woher die Idee stammt, dass Strukturen für Daten sind und Objekte nicht. Eine Struktur in C # ist nur ein Mechanismus zum Zuweisen auf dem Stapel. Anstelle einer Kopie eines Objektzeigers werden Kopien der gesamten Struktur weitergegeben.
mike30

2
@mike - C # -Strukturen werden nicht unbedingt auf dem Stapel zugeordnet.
Lee

1
@mike Die Idee "structs are for data" stammt wahrscheinlich aus der Gewohnheit, die sich viele Jahre in der C-Programmierung angeeignet haben. C hat keine Klassen und seine Strukturen sind reine Datencontainer.
Konamiman

Offensichtlich gibt es mindestens 3 C-Programmierer (die aufgestimmt wurden), die die Antwort von Michael K gelesen haben ;-)
bytedev

10

Der wichtigste Unterschied zwischen a classund a structist, was in der folgenden Situation passiert:

  Sache thing1 = new Thing ();
  thing1.somePropertyOrField = 5;
  Sache thing2 = thing1;
  thing2.somePropertyOrField = 9;

Auf was soll sich die letzte Aussage auswirken thing1.somePropertyOrField? Wenn es sich bei Thingeiner Struktur und somePropertyOrFieldum ein verfügbares öffentliches Feld handelt, werden die Objekte thing1und thing2voneinander "getrennt", sodass sich die letztere Anweisung nicht auswirkt thing1. Wenn Thinges sich um eine Klasse handelt, werden thing1und thing2aneinander angehängt, und so wird die letztere Anweisung geschrieben thing1.somePropertyOrField. Eine Struktur sollte in Fällen verwendet werden, in denen die erstere Semantik sinnvoller wäre, und eine Klasse sollte in Fällen verwendet werden, in denen die letztere Semantik sinnvoller wäre.

Beachten Sie, dass einige Leute raten, dass der Wunsch, etwas veränderbar zu machen, ein Argument dafür ist, dass es die Klasse ist. Ich würde jedoch das Gegenteil vermuten: Wenn etwas existiert, um einige Daten zu speichern, wird es veränderbar sein, und Wenn nicht klar ist, ob Instanzen an irgendetwas gebunden sind, sollte das Ding eine Struktur sein (wahrscheinlich mit exponierten Feldern), um zu verdeutlichen, dass Instanzen an nichts anderes gebunden sind.

Betrachten Sie zum Beispiel die Anweisungen:

  Person somePerson = myPeople.GetPerson ("123-45-6789");
  somePerson.Name = "Mary Johnson"; // War "Mary Smith"

Ändert die zweite Anweisung die in gespeicherten Informationen myPeople? Wenn Persones sich um eine Struktur mit exponierten Feldern handelt, wird dies nicht der Fall sein , und die Tatsache, dass dies nicht der Fall ist, ist eine offensichtliche Folge davon, dass es sich um eine Struktur mit exponierten Feldern handelt . Wenn Persones eine Struktur ist und man sie aktualisieren myPeoplemöchte, müsste man eindeutig so etwas tun myPeople.UpdatePerson("123-45-6789", somePerson). Wenn Persones sich jedoch um eine Klasse handelt, ist es möglicherweise viel schwieriger zu bestimmen, ob der obige Code den Inhalt von niemals aktualisieren MyPeople, immer aktualisieren oder manchmal aktualisieren würde.

In Bezug auf die Vorstellung, dass Strukturen "unveränderlich" sein sollten, bin ich im Allgemeinen anderer Meinung. Es gibt gültige Anwendungsfälle für "unveränderliche" Strukturen (bei denen Invarianten in einem Konstruktor erzwungen werden), aber es ist umständlich, verschwenderisch und eher dazu geneigt, Fehler zu verursachen, als sie nur anzuzeigen Felder direkt. PhoneNumberStellen Sie sich zum Beispiel eine Struktur vor, deren Felder unter anderem AreaCodeund umfassen Exchange, und nehmen Sie an, eine hat ein List<PhoneNumber>. Der Effekt des Folgenden sollte ziemlich klar sein:

  für (int i = 0; i <myList.Count; i ++)
  {
    PhoneNumber theNumber = myList [i];
    if (theNumber.AreaCode == "312")
    {
      string newExchange = "";
      if (new312to708Exchanges.TryGetValue (theNumber.Exchange), out newExchange)
      {
        theNumber.AreaCode = "708";
        theNumber.Exchange = newExchange;
        myList [i] = theNumber;
      }
    }
  }

Beachten Sie, dass der obige Code keine Felder PhoneNumberaußer AreaCodeund kennt oder kümmert Exchange. Wenn PhoneNumberes sich um eine sogenannte "unveränderliche" Struktur handeln würde, müsste entweder withXXfür jedes Feld eine Methode angegeben werden, die eine neue Strukturinstanz zurückgibt, die den übergebenen Wert im angegebenen Feld enthält, oder es wäre erforderlich für Code wie den obigen, um über jedes Feld in der Struktur Bescheid zu wissen. Nicht gerade ansprechend.

Übrigens gibt es mindestens zwei Fälle, in denen Strukturen möglicherweise Verweise auf veränderbare Typen enthalten.

  1. Die Semantik der Struktur gibt an, dass sie die Identität des betreffenden Objekts speichert und nicht als Abkürzung zum Speichern der Objekteigenschaften. Zum Beispiel würde ein "KeyValuePair" die Identitäten bestimmter Schaltflächen enthalten, es wird jedoch nicht erwartet, dass es dauerhafte Informationen über die Positionen dieser Schaltflächen, Hervorhebungszustände usw. enthält.
  2. Die Struktur weiß, dass sie die einzige Referenz auf dieses Objekt enthält, niemand anderes wird eine Referenz erhalten, und alle Mutationen, die jemals an diesem Objekt durchgeführt werden, werden durchgeführt, bevor eine Referenz darauf irgendwo gespeichert wird.

Im ersten Szenario ist die IDENTITÄT des Objekts unveränderlich. Im zweiten Fall erzwingt die Klasse des verschachtelten Objekts möglicherweise keine Unveränderlichkeit, während die Struktur, die die Referenz enthält, dies tut.


7

Ich neige dazu, Strukturen nur dann zu verwenden, wenn eine Methode benötigt wird, wodurch eine Klasse zu "schwer" erscheint. Daher behandle ich Strukturen manchmal als leichte Objekte. ACHTUNG: Dies funktioniert nicht immer und ist auch nicht immer das Beste. Es kommt also wirklich auf die Situation an!

ZUSÄTZLICHE RESSOURCEN

Für eine formellere Erklärung sagt MSDN: http://msdn.microsoft.com/en-us/library/ms229017.aspx Dieser Link überprüft, was ich oben erwähnt habe. Strukturen sollten nur für die Unterbringung einer kleinen Datenmenge verwendet werden.


5
Fragen auf einer anderen Site (auch wenn es sich bei dieser Site um einen Stapelüberlauf handelt ) gelten nicht als Duplikate. Bitte erweitern Sie Ihre Antwort um eine tatsächliche Antwort und nicht nur um Links zu anderen Orten. Wenn sich die Links jemals ändern, ist Ihre Antwort in der jetzigen Fassung unbrauchbar und wir möchten Informationen bewahren und Programmierer so unabhängig wie möglich machen. Vielen Dank!
Adam Lear

2
@Anna Lear Danke, dass du mich informiert hast, ich werde das von nun an berücksichtigen!
rrazd

2
-1 "Ich neige dazu, Strukturen nur dann zu verwenden, wenn eine Methode benötigt wird, wodurch eine Klasse zu" schwer "erscheint." ... schwer? was heißt das eigentlich ... und sagen Sie wirklich, dass es nur eine Methode gibt, dann sollte es wahrscheinlich eine Struktur sein?
Bytedev

2

Verwenden Sie eine Struktur für reine Datenkonstrukte und eine Klasse für Objekte mit Operationen.

Wenn Ihre Datenstruktur keine Zugriffskontrolle benötigt und keine anderen speziellen Vorgänge als get / set hat, verwenden Sie eine Struktur. Dies macht deutlich, dass diese Struktur nur ein Container für Daten ist.

Wenn Ihre Struktur Vorgänge enthält, die die Struktur auf komplexere Weise ändern, verwenden Sie eine Klasse. Eine Klasse kann auch mit anderen Klassen über Argumente zu Methoden interagieren.

Betrachten Sie es als den Unterschied zwischen einem Ziegelstein und einem Flugzeug. Ein Ziegelstein ist eine Struktur; Es hat Länge, Breite und Höhe. Es kann nicht viel bewirken. Ein Flugzeug könnte mehrere Operationen haben, die es irgendwie mutieren, wie das Bewegen von Steuerflächen und das Ändern des Triebwerksschubs. Es wäre besser als eine Klasse.


2
"Verwenden Sie eine Struktur für reine Datenkonstrukte und eine Klasse für Objekte mit Operationen" in C # ist dies Unsinn. Siehe meine Antwort unten.
Bytedev

1
"Verwenden Sie eine Struktur für reine Datenkonstrukte und eine Klasse für Objekte mit Operationen." Dies gilt für C / C ++, nicht für C #
taktak004
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.