Neues Objekt erstellen oder jedes Objekt zurücksetzen?

29
 public class MyClass
    {
        public object Prop1 { get; set; }

        public object Prop2 { get; set; }

        public object Prop3 { get; set; }
    }

Angenommen, ich habe ein Objekt myObjectvon MyClassund muss seine Eigenschaften zurücksetzen. Ist es besser, ein neues Objekt zu erstellen oder jede Eigenschaft neu zuzuweisen? Angenommen, ich habe keine zusätzliche Verwendung mit der alten Instanz.

myObject = new MyClass();

oder

myObject.Prop1 = null;
myObject.Prop2 = null;
myObject.Prop3 = null;
Glocken
quelle
14
Kann ohne Kontext nicht wirklich beantwortet werden.
CodesInChaos
2
@CodesInChaos, in der Tat. Spezifisches Beispiel, bei dem das Erstellen neuer Objekte möglicherweise nicht vorzuziehen ist: Objekt-Pooling und der Versuch, die Zuordnungen für den Umgang mit dem GC zu minimieren.
XNargaHuntress
@ XGundam05 Aber selbst dann ist es sehr unwahrscheinlich, dass diese im OP vorgeschlagene Technik die geeignete Art ist, damit umzugehen
Ben Aaronson,
2
Suppose I have an object myObject of MyClass and I need to reset its properties- Wenn Sie die Eigenschaften Ihres Objekts zurücksetzen müssen oder die Problemdomäne modellieren möchten, erstellen Sie eine reset()Methode für MyClass. Siehe HarrisonPaines Antwort unten
Brandin

Antworten:

49

Das Instanziieren eines neuen Objekts ist immer besser, dann haben Sie 1 Stelle, um die Eigenschaften (den Konstruktor) zu initialisieren und können sie problemlos aktualisieren.

Stellen Sie sich vor, Sie fügen der Klasse eine neue Eigenschaft hinzu. Sie möchten lieber den Konstruktor aktualisieren, als eine neue Methode hinzuzufügen, mit der auch alle Eigenschaften neu initialisiert werden.

Es gibt Fälle, in denen Sie ein Objekt möglicherweise wiederverwenden möchten, in denen die Neuinitialisierung einer Eigenschaft sehr teuer ist und Sie sie behalten möchten. Dies wäre jedoch spezialisierter und Sie hätten spezielle Methoden, um alle anderen Eigenschaften neu zu initialisieren. Manchmal möchten Sie auch in dieser Situation noch ein neues Objekt erstellen.

gbjbaanb
quelle
6
Mögliche dritte Option: myObject = new MyClass(oldObject);. Dies würde jedoch eine exakte Kopie des ursprünglichen Objekts in einer neuen Instanz implizieren.
AmazingDreams
Ich denke, das new MyClass(oldObject)ist die beste Lösung für die Fälle, in denen die Initialisierung teuer ist.
Rickcnagy
8
Copy-Konstruktoren haben mehr C ++ - Stil. .NET scheint eine Clone () -Methode zu bevorzugen, die eine neue Instanz zurückgibt, die eine exakte Kopie ist.
Eric
2
Der Konstruktor kann einfach nur eine öffentliche Initialize () -Methode aufrufen, sodass Sie immer noch diesen einzelnen Initialisierungspunkt haben, der an einem beliebigen Punkt aufgerufen werden kann, um effektiv ein "neues" frisches Objekt zu erstellen. Ich habe Bibliotheken verwendet, die so etwas wie eine IInitialize-Schnittstelle für Objekte haben, die in Objektpools verwendet und für die Leistung wiederverwendet werden können.
Chad Schouggins
16

Sie sollten es definitiv vorziehen, in den allermeisten Fällen ein neues Objekt zu erstellen . Probleme beim erneuten Zuweisen aller Eigenschaften:

  • Erfordert öffentliche Setter für alle Eigenschaften, wodurch das von Ihnen zur Verfügung gestellte Verkapselungsniveau drastisch eingeschränkt wird
  • Wenn Sie wissen, ob Sie für die alte Instanz eine zusätzliche Verwendung haben, müssen Sie überall wissen, dass die alte Instanz verwendet wird. Wenn class Aund class Balso beide übergebene Instanzen von class Csind, müssen sie wissen, ob sie jemals die gleiche Instanz erhalten werden und wenn ja, ob die andere Instanz sie noch verwendet. Dies verbindet eng Klassen, die sonst keinen Grund haben zu sein.
  • Führt zu wiederholtem Code. Wenn Sie dem Konstruktor einen Parameter hinzufügen, besteht, wie in gbjbaanb angegeben, keine Gefahr, dass ein Punkt übersehen wird, wenn der Konstruktor nicht kompiliert wird. Wenn Sie nur eine öffentliche Eigenschaft hinzufügen, müssen Sie sicherstellen, dass Sie jeden Ort, an dem Objekte "zurückgesetzt" werden, manuell finden und aktualisieren.
  • Erhöht die Komplexität. Stellen Sie sich vor, Sie erstellen und verwenden eine Instanz der Klasse in einer Schleife. Wenn Sie Ihre Methode verwenden, müssen Sie jetzt eine separate Initialisierung durchführen, wenn Sie zum ersten Mal die Schleife durchlaufen oder bevor die Schleife startet. In beiden Fällen müssen Sie zusätzlichen Code schreiben, um diese beiden Arten der Initialisierung zu unterstützen.
  • Dies bedeutet, dass Ihre Klasse sich nicht davor schützen kann, in einem ungültigen Zustand zu sein. Stellen Sie sich vor, Sie schreiben eine FractionKlasse mit einem Zähler und einem Nenner und möchten erzwingen, dass sie immer reduziert wird (dh der GCD des Zählers und des Nenners ist 1). Dies ist unmöglich, wenn Sie zulassen möchten, dass Benutzer den Zähler und den Nenner öffentlich festlegen, da sie möglicherweise durch einen ungültigen Status wechseln, um von einem gültigen Status in einen anderen zu gelangen. ZB 1/2 (gültig) -> 2/2 (ungültig) -> 2/3 (gültig).
  • Ist überhaupt nicht idiomatisch für die Sprache, in der Sie arbeiten, und erhöht die kognitive Reibung für jeden, der den Code pflegt.

Dies sind alles ziemlich bedeutende Probleme. Und was Sie als Gegenleistung für die zusätzliche Arbeit erhalten, die Sie erstellen, ist ... nichts. Das Erstellen von Instanzen von Objekten ist im Allgemeinen unglaublich kostengünstig, sodass der Leistungsvorteil fast immer vernachlässigbar ist.

Wie in der anderen Antwort bereits erwähnt, kann die Leistung nur dann relevant sein, wenn Ihre Klasse einige erheblich teure Arbeiten am Bau ausführt. Aber selbst in diesem Fall müssen Sie in der Lage sein, den teuren Teil von den Eigenschaften, die Sie zurücksetzen, zu trennen, damit diese Technik funktioniert. Daher können Sie stattdessen das Fliegengewichtmuster oder ähnliches verwenden.


Als Randnotiz, einige der oben genannten Probleme könnten etwas gemildert werden, indem keine Setter verwendet werden und stattdessen eine public ResetMethode für Ihre Klasse verwendet wird, die dieselben Parameter wie der Konstruktor verwendet. Wenn Sie aus irgendeinem Grund diesen Reset-Weg einschlagen möchten, wäre dies wahrscheinlich eine viel bessere Möglichkeit.

Dennoch ist die zusätzliche Komplexität und Wiederholung, die hinzukommt, zusammen mit den oben genannten Punkten, die nicht angesprochen werden, immer noch ein sehr überzeugendes Argument dagegen, insbesondere wenn die nicht vorhandenen Vorteile gegeneinander abgewogen werden.

Ben Aaronson
quelle
Ich denke, ein weitaus wichtiger Anwendungsfall für das Zurücksetzen und nicht für das Erstellen eines neuen Objekts ist, wenn Sie das Objekt tatsächlich zurücksetzen. IE. Wenn Sie möchten, dass andere Klassen, die auf das Objekt zeigen, nach dem Zurücksetzen ein neues Objekt sehen.
Taemyr
@Taemyr Möglicherweise, aber das OP schließt das in der Frage ausdrücklich aus
Ben Aaronson
9

Angesichts des sehr allgemeinen Beispiels ist es schwer zu sagen. Wenn das Zurücksetzen der Eigenschaften im Fall der Domäne semantisch sinnvoll ist, ist es für den Konsumenten Ihrer Klasse sinnvoller, sie aufzurufen

MyObject.Reset(); // Sets all necessary properties to null

Als

MyObject = new MyClass();

Ich würde NIEMALS verlangen, den Verbraucher Ihrer Klasse anzurufen

MyObject.Prop1 = null;
MyObject.Prop2 = null; // and so on

Wenn die Klasse etwas darstellt, das zurückgesetzt werden kann, sollte sie diese Funktionalität über eine Reset()Methode verfügbar machen, anstatt sich darauf zu verlassen, den Konstruktor aufzurufen oder seine Eigenschaften manuell festzulegen.

Harrison Paine
quelle
5

Wie Harrison Paine und Brandin vorschlagen, würde ich dasselbe Objekt wiederverwenden und die Initialisierung der Eigenschaften in einer Reset-Methode faktorisieren:

public class MyClass
{
    public MyClass() { this.Reset() }

    public void Reset() {
        this.Prop1 = whatever
        this.Prop2 = you name it
        this.Prop3 = oh yeah
    }

    public object Prop1 { get; set; }

    public object Prop2 { get; set; }

    public object Prop3 { get; set; }
}
Antoine Trouve
quelle
Genau das wollte ich beantworten. Dies ist das Beste aus beiden Welten - und je nach Anwendungsfall die einzig gangbare Wahl (Instance-Pooling)
Falco
2

Wenn das beabsichtigte Verwendungsmuster für eine Klasse ist, dass ein einzelner Besitzer einen Verweis auf jede Instanz speichert, werden in keinem anderen Code Kopien der Verweise gespeichert, und es kommt sehr häufig vor, dass Besitzer Schleifen haben, die mehrere Male ausgeführt werden müssen. " Füllen Sie "eine leere Instanz" aus, verwenden Sie sie vorübergehend und brauchen Sie sie nie wieder (eine gemeinsame Klasse, die ein solches Kriterium erfüllt, wäre das StringBuilder). Unter Performance-Gesichtspunkten kann es für die Klasse nützlich sein, eine Methode zum Zurücksetzen einer Instanz auf "Gefällt mir" einzuschließen. neue Bedingung. Eine solche Optimierung ist wahrscheinlich nicht viel wert, wenn für die Alternative nur einige hundert Instanzen erstellt werden müssten. Die Kosten für die Erstellung von Millionen oder Milliarden von Objektinstanzen können sich jedoch summieren.

In einem verwandten Hinweis gibt es einige Muster, die für Methoden verwendet werden können, die Daten in einem Objekt zurückgeben müssen:

  1. Methode erstellt neues Objekt; Gibt eine Referenz zurück.

  2. Methode akzeptiert einen Verweis auf ein veränderbares Objekt und füllt es aus.

  3. Methode akzeptiert eine Variable vom Referenztyp als refParameter und verwendet entweder das vorhandene Objekt, falls dies geeignet ist, oder ändert die Variable, um ein neues Objekt zu identifizieren.

Der erste Ansatz ist oft semantisch der einfachste. Der zweite ist auf der anrufenden Seite etwas umständlich, bietet jedoch möglicherweise eine bessere Leistung, wenn ein Anrufer häufig ein Objekt erstellen und tausendfach verwenden kann. Der dritte Ansatz ist semantisch etwas umständlich, kann jedoch hilfreich sein, wenn eine Methode Daten in einem Array zurückgeben muss und der Aufrufer die erforderliche Arraygröße nicht kennt. Wenn der aufrufende Code den einzigen Verweis auf das Array enthält, entspricht das Umschreiben dieses Verweises mit einem Verweis auf ein größeres Array semantisch dem einfachen Vergrößern des Arrays (was semantisch das gewünschte Verhalten ist). Die Verwendung von List<T>Arrays ist in vielen Fällen unter Umständen besser als die Verwendung eines Arrays mit manueller Größenänderung. Arrays mit Strukturen bieten jedoch eine bessere Semantik und Leistung als Listen mit Strukturen.

Superkatze
quelle
1

Ich denke, den meisten Menschen, die sich für die Erstellung eines neuen Objekts aussprechen, fehlt ein kritisches Szenario: die Garbage Collection (GC). GC kann in Anwendungen, in denen viele Objekte erstellt werden (z. B. Denkspiele oder wissenschaftliche Anwendungen), zu einem echten Leistungseinbruch führen.

Angenommen, ich habe einen Ausdrucksbaum, der einen mathematischen Ausdruck darstellt, wobei in den inneren Knoten Funktionsknoten (ADD, SUB, MUL, DIV) und die Blattknoten Endknoten (X, e, PI) sind. Wenn meine Knotenklasse eine Evaluate () -Methode hat, ruft sie wahrscheinlich rekursiv die untergeordneten Knoten auf und sammelt Daten über einen Datenknoten. Zumindest müssen alle Blattknoten ein Datenobjekt erstellen, und diese können dann auf dem Weg nach oben im Baum wiederverwendet werden, bis der endgültige Wert ausgewertet ist.

Angenommen, ich habe Tausende dieser Bäume und ich habe sie in einer Schleife ausgewertet. Alle diese Datenobjekte lösen einen GC aus und verursachen einen großen Leistungseinbruch (bis zu 40% CPU-Auslastungsverlust für einige Läufe in meiner App - ich habe einen Profiler ausgeführt, um die Daten abzurufen).

Eine mögliche Lösung? Verwenden Sie diese Datenobjekte erneut und rufen Sie sie einfach mit .Reset () auf, nachdem Sie sie verwendet haben. Die Blattknoten rufen nicht länger 'new Data ()' auf, sondern eine Factory-Methode, die den Lebenszyklus des Objekts abwickelt.

Ich weiß das, weil ich eine Anwendung habe, die dieses Problem behoben hat.

Jonathan Lavering
quelle