Crea un nuovo oggetto o reimposta ogni proprietà?


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

        public object Prop2 { get; set; }

        public object Prop3 { get; set; }
    }

Supponiamo che io sono un oggetto myObjectdi MyClasse ho bisogno di ripristinare le sue proprietà, è meglio creare un nuovo oggetto o riassegnare ogni proprietà? Supponiamo che non abbia alcun uso aggiuntivo con la vecchia istanza.

myObject = new MyClass();

o

myObject.Prop1 = null;
myObject.Prop2 = null;
myObject.Prop3 = null;


14
Non si può davvero rispondere senza contesto.
CodesInChaos,

2
@CodesInChaos, davvero. Esempio specifico in cui la creazione di nuovi oggetti potrebbe non essere preferibile: pooling di oggetti e tentativo di minimizzare le allocazioni per la gestione del GC.
XNargaHuntress il

@ XGundam05 Ma anche allora, è molto improbabile che questa tecnica suggerita nel PO sia il modo appropriato di affrontarla
Ben Aaronson,

2
Suppose I have an object myObject of MyClass and I need to reset its properties- Se è necessario ripristinare le proprietà dell'oggetto o se è utile modellare il dominio problematico, perché non creare un reset()metodo per MyClass. Vedi la risposta di HarrisonPaine di seguito
Brandin,

Risposte:


49

L'istanza di un nuovo oggetto è sempre migliore, quindi hai 1 posto per inizializzare le proprietà (il costruttore) e puoi aggiornarlo facilmente.

Immagina di aggiungere una nuova proprietà alla classe, preferiresti aggiornare il costruttore piuttosto che aggiungere un nuovo metodo che reinizializza anche tutte le proprietà.

Ora, ci sono casi in cui potresti voler riutilizzare un oggetto, uno in cui una proprietà è molto costosa da reinizializzare e vorresti conservarla. Questo sarebbe comunque più specializzato e avresti metodi speciali per reinizializzare tutte le altre proprietà. Ti piacerebbe comunque creare un nuovo oggetto a volte anche per questa situazione.


6
Possibile terza opzione: myObject = new MyClass(oldObject);. Anche se questo implicherebbe una copia esatta dell'oggetto originale in una nuova istanza.
AmazingDreams,

Penso che new MyClass(oldObject)sia la soluzione migliore per i casi in cui l'inizializzazione è costosa.
rickcnagy,

8
I costruttori di copie sono più in stile C ++. .NET sembra favorire un metodo Clone () che restituisce una nuova istanza che è una copia esatta.
Eric

2
Il constuctor non potrebbe fare altro che chiamare un metodo Initialize () pubblico in modo da avere ancora quel singolo punto di inizializzazione che potrebbe essere invocato un qualsiasi punto per creare efficacemente un "nuovo" oggetto nuovo. Ho usato librerie che hanno qualcosa come un'interfaccia IInitialize per gli oggetti che possono essere utilizzati nei pool di oggetti e riutilizzati per le prestazioni.
Chad Schouggins,

16

Dovresti assolutamente preferire la creazione di un nuovo oggetto nella stragrande maggioranza dei casi. Problemi con la riassegnazione di tutte le proprietà:

  • Richiede setter pubblici su tutte le proprietà, il che limita drasticamente il livello di incapsulamento che è possibile fornire
  • Sapere se hai un uso aggiuntivo per la vecchia istanza significa che devi sapere ovunque che viene utilizzata la vecchia istanza. Quindi, se classe Ae classe Bsono entrambe istanze passate della classe C, devono sapere se passeranno mai la stessa istanza e, in tal caso, se l'altra la sta ancora usando. Questo accoppia strettamente le classi che altrimenti non hanno motivo di esserlo.
  • Porta a un codice ripetuto, come indicato da gbjbaanb, se aggiungi un parametro al costruttore, ovunque la chiamata al costruttore non verrà compilata, non c'è pericolo di perdere un posto. Se aggiungi semplicemente una proprietà pubblica, dovrai essere sicuro di trovare e aggiornare manualmente ogni luogo in cui gli oggetti vengono "ripristinati".
  • Aumenta la complessità. Immagina di creare e utilizzare un'istanza della classe in un ciclo. Se usi il tuo metodo, ora devi eseguire un'inizializzazione separata la prima volta attraverso il ciclo o prima che inizi il ciclo. In entrambi i casi è necessario scrivere codice aggiuntivo per supportare questi due modi di inizializzazione.
  • Significa che la tua classe non può proteggersi dall'essere in uno stato non valido. Immagina di aver scritto una Fractionclasse con un numeratore e un denominatore e che volevi imporre che fosse sempre ridotta (cioè il gcd del numeratore e del denominatore era 1). Questo è impossibile da fare bene se si desidera consentire alle persone di impostare pubblicamente il numeratore e il denominatore, poiché possono passare da uno stato non valido a passare da uno stato valido a un altro. Ad esempio 1/2 (valido) -> 2/2 (non valido) -> 2/3 (valido).
  • Non è affatto idiomatico per la lingua in cui stai lavorando, aumentare l'attrito cognitivo per chiunque mantenga il codice.

Questi sono tutti problemi abbastanza significativi. E ciò che ottieni in cambio del lavoro extra che crei è ... niente. La creazione di istanze di oggetti è, in generale, incredibilmente economica, quindi il vantaggio in termini di prestazioni sarà quasi sempre trascurabile.

Come menzionato nell'altra risposta, l'unica volta in cui le prestazioni potrebbero essere una preoccupazione rilevante è se la tua classe esegue lavori di costruzione significativamente costosi. Ma anche in quel caso, per far funzionare questa tecnica dovresti essere in grado di separare la parte costosa dalle proprietà che stai reimpostando, quindi potresti utilizzare invece il modello flyweight o simile.


Come nota a margine, alcuni dei problemi sopra potrebbero essere mitigati in qualche modo non usando setter e avendo invece un public Resetmetodo sulla tua classe che accetta gli stessi parametri del costruttore. Se per qualche motivo volessi seguire questa via di ripristino, probabilmente sarebbe un modo molto migliore di farlo.

Tuttavia, l'ulteriore complessità e la ripetizione che aggiunge, insieme ai punti sopra indicati che non affronta, sono ancora un argomento molto persuasivo contro il farlo, specialmente se valutato rispetto ai benefici inesistenti.


Penso che un caso d'uso molto più importante per il ripristino piuttosto che per la creazione di un nuovo oggetto sia se si sta effettivamente ripristinando l'oggetto. IE. se si desidera che altre classi che puntano all'oggetto vedano un nuovo oggetto dopo averlo ripristinato.
Taemyr,

@Taemyr Forse, ma il PO lo esclude esplicitamente nella domanda
Ben Aaronson,

9

Dato l'esempio molto generico, è difficile da dire. Se "reimpostare le proprietà" ha un senso semantico nel caso del dominio, avrà più senso chiamare il consumatore della tua classe

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

Di

MyObject = new MyClass();

Non richiederei MAI di effettuare la chiamata in classe al consumatore

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

Se la classe rappresenta qualcosa che può essere ripristinato, dovrebbe esporre quella funzionalità attraverso un Reset()metodo, piuttosto che fare affidamento sul chiamare il costruttore o impostarne manualmente le proprietà.


5

Come suggeriscono Harrison Paine e Brandin, riutilizzerei lo stesso oggetto e fattorizzerei l'inizializzazione delle proprietà in un metodo di ripristino:

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; }
}

Esattamente quello a cui volevo rispondere. Questo è il migliore dei due mondi - E a seconda del caso d'uso, l'unica scelta praticabile (Instance-Pooling)
Falco,

2

Se il modello di utilizzo previsto per una classe è che un singolo proprietario manterrà un riferimento a ciascuna istanza, nessun altro codice conserverà copie dei riferimenti e sarà molto comune per i proprietari disporre di loop che devono, molte volte , " compila "un'istanza vuota, usala temporaneamente e non ne avrai mai più bisogno (una classe comune che soddisfa un tale criterio sarebbe StringBuilder) quindi potrebbe essere utile dal punto di vista delle prestazioni per la classe includere un metodo per ripristinare un'istanza come- Nuova Condizione. Un'ottimizzazione del genere non varrebbe molto se l'alternativa richiederebbe solo la creazione di alcune centinaia di istanze, ma il costo della creazione di milioni o miliardi di istanze di oggetti può sommarsi.

In una nota correlata, ci sono alcuni modelli che possono essere utilizzati per i metodi che devono restituire i dati in un oggetto:

  1. Il metodo crea un nuovo oggetto; restituisce riferimento.

  2. Il metodo accetta un riferimento a un oggetto mutabile e lo compila.

  3. Il metodo accetta una variabile del tipo di riferimento come refparametro e utilizza l'oggetto esistente, se appropriato, oppure modifica la variabile per identificare un nuovo oggetto.

Il primo approccio è spesso il più semplice semanticamente. Il secondo è un po 'imbarazzante dal lato della chiamata, ma può offrire prestazioni migliori se un chiamante sarà spesso in grado di creare un oggetto e usarlo migliaia di volte. Il terzo approccio è un po 'imbarazzante semanticamente, ma può essere utile se un metodo dovrà restituire i dati in un array e il chiamante non conoscerà la dimensione dell'array richiesta. Se il codice chiamante contiene l'unico riferimento all'array, riscrivere quel riferimento con un riferimento a un array più grande sarà semanticamente equivalente a rendere l'array più grande (che è il comportamento desiderato semanticamente). Sebbene List<T>in molti casi l' utilizzo possa essere migliore dell'utilizzo di un array ridimensionato manualmente, gli array di strutture offrono una semantica e prestazioni migliori rispetto agli elenchi di strutture.


1

Penso che la maggior parte delle persone che rispondono a favore della creazione di un nuovo oggetto manchi di uno scenario critico: garbage collection (GC). GC può avere un vero successo prestazionale in applicazioni che creano molti oggetti (pensa a giochi o applicazioni scientifiche).

Diciamo che ho un albero di espressioni che rappresenta un'espressione matematica, in cui nei nodi interni sono nodi di funzione (ADD, SUB, MUL, DIV) e i nodi foglia sono nodi terminali (X, e, PI). Se la mia classe di nodo ha un metodo Evaluate (), probabilmente chiamerà ricorsivamente i figli e raccoglierà i dati tramite un nodo Dati. Per lo meno, tutti i nodi foglia dovranno creare un oggetto Dati e quindi questi potranno essere riutilizzati durante la salita dell'albero fino a quando non viene valutato il valore finale.

Ora diciamo che ho migliaia di questi alberi e li ho valutati in un ciclo. Tutti quegli oggetti Data attiveranno un GC e causeranno un calo delle prestazioni, un grosso problema (fino al 40% di perdita di utilizzo della CPU per alcune esecuzioni nella mia app: ho eseguito un profiler per ottenere i dati).

Una possibile soluzione? Riutilizzare quegli oggetti dati e chiamare alcuni .Reset () su di essi dopo aver finito di usarli. I nodi foglia non chiameranno più "nuovi dati ()", chiameranno alcuni metodi Factory che gestiscono il ciclo di vita dell'oggetto.

Lo so perché ho un'applicazione che ha riscontrato questo problema e questo l'ha risolto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.