9 Stimmen

Sollte ich struct oder class verwenden?

Ich befinde mich in einem klassischen Design-Dilemma. Ich schreibe eine C#-Datenstruktur für einen Wert und Maßeinheit Tupel (z.B. 7,0 Millimeter) und ich frage mich, ob ich einen Referenztyp oder einen Werttyp verwenden sollte.

Die Vorteile einer Struktur sollte es weniger Heap-Aktionen geben, was zu einer besseren Leistung bei Ausdrücken und einer geringeren Belastung des Garbage Collectors führt. Dies wäre normalerweise meine Wahl für einen einfachen Typ wie diesen, aber in diesem konkreten Fall gibt es Nachteile.

Das Tupel ist Teil eines recht allgemeinen Rahmens für Analyseergebnisse, bei dem die Ergebnisse in einer WPF-Anwendung je nach Typ des Ergebniswerts auf unterschiedliche Weise dargestellt werden. Diese Art der schwachen Typisierung wird von WPF mit all seinen Datenvorlagen, Wertkonvertierungen und Vorlagenselektoren außergewöhnlich gut gehandhabt. Die Implikation ist, dass der Wert eine Menge von Boxing / Unboxing durchlaufen wird, wenn mein Tupel als Struktur dargestellt wird. Tatsächlich wird die Verwendung des Tupels in Ausdrücken geringer sein als die Verwendung in Boxing-Szenarien. Um das ganze Boxing zu vermeiden, überlege ich, meinen Typ als Klasse zu deklarieren. Eine weitere Sorge über eine Struktur ist, dass es Fallstricke mit Zwei-Wege-Bindung in WPF sein könnte, da es einfacher wäre, am Ende mit Kopien der Tupel irgendwo im Code statt Referenzkopien.

Ich habe auch einige bequeme Operator-Überladung. Mit überladenen Vergleichsoperatoren kann ich beispielsweise problemlos Millimeter mit Zentimetern vergleichen. Allerdings gefällt mir die Idee nicht, == und != zu überladen, wenn mein Tupel eine Klasse ist, da die Konvention besagt, dass == und != ReferenceEquals für Referenztypen ist (im Gegensatz zu System.String, was eine andere klassische Diskussion ist). Wenn == und != überladen wird, wird jemand schreiben if (myValue == null) und eine unangenehme Laufzeitausnahme erhalten, wenn myValue sich eines Tages als null herausstellt.

Ein weiterer Aspekt ist, dass es in C# (anders als z.B. in C++) keine klare Möglichkeit gibt, Referenz- und Werttypen im Code zu unterscheiden, obwohl die Semantik sehr unterschiedlich ist. Ich befürchte, dass Benutzer meines Tupels (wenn es als struct deklariert ist) davon ausgehen, dass der Typ eine Klasse ist, da die meisten benutzerdefinierten Datenstrukturen dies sind und eine Referenzsemantik annehmen. Das ist ein weiteres Argument, warum man Klassen bevorzugen sollte, einfach weil es das ist, was der Benutzer erwartet und es gibt keine "." / "->", um sie zu unterscheiden. Im Allgemeinen würde ich fast immer eine Klasse verwenden, es sei denn, mein Profiler sagt mir, eine Struktur zu verwenden, einfach weil die Klassensemantik am ehesten von anderen Programmierern erwartet wird und C# nur vage Hinweise gibt, ob es das eine oder das andere ist.

Meine Fragen lauten also:

Welche anderen Überlegungen sollte ich anstellen, wenn ich entscheide, ob ich mich für einen Wert- oder einen Referenzwert entscheiden soll?

Kann == / != Überladung in einer Klasse gerechtfertigt sein in jede Umstände?

Programmierer nehmen Dinge an. Die meisten würden wahrscheinlich davon ausgehen, dass etwas namens "Punkt" ein Wertetyp ist. Was würden Sie annehmen, wenn Sie einen Code mit einem "UnitValue" lesen würden?

Was würden Sie angesichts meiner Nutzungsbeschreibung wählen?

0voto

Jon Hanna Punkte 106367

Datenstruktur zur Aufnahme eines Tupels aus Wert und Maßeinheit (z. B. 7,0 Millimeter)

Das klingt, als hätte es eine Wertesemantik. Das Framework bietet einen Mechanismus zur Erstellung von Typen mit Wertesemantik, nämlich struct . Verwenden Sie das.

Fast alles, was Sie im nächsten Absatz in Ihrer Frage sagen, sowohl pro als auch contra Werttypen ist eine Frage der Optimierung auf der Grundlage, wie es mit für die Implementierungsdetails der Laufzeit interagieren wird. Da es in dieser Hinsicht sowohl Vor- als auch Nachteile gibt, gibt es keinen eindeutigen Effizienzgewinner. Da man den eindeutigen Effizienzgewinner nicht finden kann, ohne es tatsächlich auszuprobieren, ist jeder Versuch, in dieser Hinsicht zu optimieren, eindeutig verfrüht. So sehr ich es auch leid bin, dass das Zitat von der verfrühten Optimierung immer dann in den Mund genommen wird, wenn jemand versucht, etwas schneller oder kleiner zu machen, hier trifft es zu.

Allerdings geht es hier nicht um Optimierung:

Mir gefällt die Idee nicht, == und != zu überladen, wenn mein Tupel eine Klasse ist, da die Konvention besagt, dass == und != ReferenceEquals für Referenztypen ist.

Das stimmt ganz und gar nicht. Die Standard ist, dass == und != sich mit Referenzgleichheit befassen, aber das ist so viel, weil es die einzige sinnvolle Vorgabe ohne mehr Wissen über die Semantik der Klasse ist. == und != sollten überladen werden, wenn es der Semantik einer Klasse entspricht, dies zu tun, ReferenceEquals sollte verwendet werden, wenn Referenzgleichheit die einzige Sache ist, um die man sich kümmert.

Wenn == und != überladen sind, wird jemand if (myValue == null) schreiben und eine böse Laufzeitausnahme erhalten, wenn sich myValue eines Tages als null herausstellt.

Nur wenn die == Überlastung einen Anfängerfehler hat. Der normale Ansatz wäre:

public static bool operator == (MyType x, MyType y)
{
  if(ReferenceEquals(x, null))
    return ReferenceEquls(y, null);
  if(ReferenceEquals(y, null))
    return false;
  return x.Equals(y);
}

Und natürlich sollte die Equals-Überladung auch prüfen, ob der Parameter null ist, und false zurückgeben, wenn dies der Fall ist, für Leute, die sie direkt aufrufen. Es gibt nicht einmal eine signifikante Auswirkung auf die Leistung des Aufrufs gegenüber dem Standard == Verhalten, wenn einer oder beide Werte null sind, was ist also die Sorge?

Ein weiterer Aspekt ist, dass es in C# (anders als z.B. in C++) keine klare Möglichkeit gibt, Referenz- und Werttypen in der Verwendung von Code zu unterscheiden, obwohl die Semantik sehr unterschiedlich ist.

Nicht wirklich. Die Standardsemantik, soweit es um Gleichheit geht, ist ziemlich unterschiedlich, aber da Sie etwas beschreiben, das eine Wertsemantik haben soll, deutet das eher darauf hin, dass es ein Wertetyp ist und nicht ein Klassentyp. Darüber hinaus ist die verfügbare Semantik weitgehend identisch. Die Mechanismen können sich in Bezug auf Boxing, Referenz-Sharing und so weiter unterscheiden, aber das ist wieder eine Frage der Optimierung.

Kann == / != Überladung in einer Klasse unter allen Umständen gerechtfertigt sein?

Ich würde eher fragen, ob es gerechtfertigt ist, == und != nicht zu überladen, wenn dies für die Klasse sinnvoll ist?

Was ich als Programmierer über "UnitValue" annehmen würde, würde ich wahrscheinlich annehmen, dass es eine Struktur ist, da es sich so anhört, als sollte es eine sein. Aber eigentlich würde ich nicht einmal davon ausgehen, dass, wie ich meist nicht kümmern, bis ich etwas mit ihm tun, wo es wichtig ist, die angesichts der Tatsache, dass es auch klingt, wie es unveränderlich sein sollte, ist eine reduzierte Menge (die semantischen Unterschiede zwischen mutable Referenztypen und mutable structs sind größer in der Praxis, aber dieses ist ein no-brainer unveränderlich).

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