Crea un nuovo oggetto o resetta ogni proprietà?

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

        public object Prop2 { get; set; }

        public object Prop3 { get; set; }
    }

Supponiamo di avere un oggetto myObject di MyClass e devo resettare 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;
    
posta Bells 25.06.2015 - 12:02
fonte

6 risposte

48

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

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

Ora, ci sono casi in cui si potrebbe voler riutilizzare un oggetto, uno in cui una proprietà è molto costosa da reinizializzare e si vorrebbe tenerla. Questo sarebbe comunque più specialistico e avresti metodi speciali per reinizializzare tutte le altre proprietà. Dovresti comunque creare un nuovo oggetto a volte anche per questa situazione.

    
risposta data 25.06.2015 - 12:06
fonte
16

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

  • Richiede setter pubblici per tutte le proprietà, che limita drasticamente il livello di incapsulamento che puoi fornire
  • Sapere se hai un uso aggiuntivo per la vecchia istanza significa che devi sapere ovunque che la vecchia istanza viene utilizzata. Quindi, se la classe A e la classe B sono entrambe istanze passate di classe C , devono sapere se verranno mai passate la stessa istanza e, in tal caso, se l'altra lo sta ancora utilizzando. Questo raggruppa strettamente classi che altrimenti non avrebbero motivo di essere.
  • Porta al codice ripetuto - come indicato da gbjbaanb, se si aggiunge un parametro al costruttore, ovunque che chiama il costruttore non riuscirà a compilare, non c'è pericolo di perdere uno spot. Se aggiungi solo una proprietà pubblica, dovrai accertarti di trovare e aggiornare manualmente ogni luogo in cui gli oggetti sono "ripristinati".
  • Aumenta la complessità. Immagina di creare e utilizzare un'istanza della classe in un ciclo. Se si utilizza il metodo, è necessario eseguire l'inizializzazione separata la prima volta attraverso il ciclo o prima dell'avvio del ciclo. In entrambi i casi è un codice aggiuntivo che devi scrivere per supportare questi due modi di inizializzare.
  • Indica che la tua classe non può proteggersi dallo stato non valido. Immagina di aver scritto una classe Fraction con un numeratore e un denominatore, e volevi far rispettare che era sempre ridotta (cioè il gcd del numeratore e denominatore era 1). Questo è impossibile da fare bene se si desidera consentire alle persone di impostare il numeratore e il denominatore pubblicamente, poiché possono passare attraverso uno stato non valido per passare da uno stato valido a un altro. Per esempio. 1/2 (valido) - > 2/2 (non valido) - > 2/3 (valido).
  • Non è affatto idiomatico per la lingua in cui lavori, aumentando l'attrito cognitivo per chiunque mantenga il codice.

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

Come l'altra risposta menzionata, l'unica prestazione in termini di tempo potrebbe essere una preoccupazione rilevante se la tua classe svolge un lavoro di costruzione molto costoso. Ma anche in questo caso, affinché questa tecnica funzioni, dovresti essere in grado di separare la parte costosa dalle proprietà che stai ripristinando, quindi potresti utilizzare modello a peso ridotto o simile.

Come nota a margine, alcuni dei problemi di cui sopra potrebbero essere in qualche modo mitigati non usando setter e invece un metodo public Reset sulla tua classe che prende gli stessi parametri del costruttore. Se per qualche motivo avresti voluto seguire questa rotta di ripristino, sarebbe probabilmente un modo molto migliore di farlo.

Tuttavia, la complessità aggiuntiva e la ripetizione che aggiunge, insieme ai punti sopra cui non si rivolge, sono ancora un argomento molto persuasivo contro il farlo, specialmente se confrontati con i benefici inesistenti.

    
risposta data 25.06.2015 - 15:03
fonte
9

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

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

di

MyObject = new MyClass();

Non avrei MAI richiesto di chiamare il consumatore della tua classe

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

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

    
risposta data 25.06.2015 - 17:08
fonte
5

Come suggeriscono Harrison Paine e Brandin, vorrei riutilizzare lo stesso oggetto e ridimensionare l'inizializzazione delle proprietà in un metodo di Reset:

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; }
}
    
risposta data 25.06.2015 - 23:38
fonte
2

Se il modello di utilizzo previsto per una classe è che un singolo proprietario manterrà un riferimento a ciascuna istanza, nessun altro codice manterrà copie dei riferimenti e sarà molto comune per i proprietari avere loop che devono, < em> molte volte , "riempire" un'istanza vuota, usarla temporaneamente e non averne mai più bisogno (una classe comune che soddisfa tale criterio sarebbe StringBuilder ), quindi potrebbe essere utile dal punto di vista delle prestazioni per la classe per includere un metodo per reimpostare un'istanza alla condizione like-new. Non è probabile che tale ottimizzazione valga molto se l'alternativa richiederebbe solo la creazione di poche centinaia di istanze, ma il costo di creazione di milioni o miliardi di istanze di oggetti può sommarsi.

Su 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 riempie.

  3. Il metodo accetta una variabile di tipo riferimento come parametro ref e utilizza l'oggetto esistente se adatto oppure modifica la variabile per identificare un nuovo oggetto.

Il primo approccio è spesso il più semplice semanticamente. Il secondo è un po 'scomodo sul lato delle chiamate, 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 'scomodo 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, la riscrittura di tale riferimento con un riferimento a un array più grande sarà semanticamente equivalente a rendere semplicemente l'array più grande (che è il comportamento desiderato semanticamente). Anche se l'utilizzo di List<T> potrebbe essere più piacevole dell'utilizzo di un array ridimensionato manualmente in molti casi, gli array di strutture offrono una semantica migliore e prestazioni migliori rispetto agli elenchi di strutture.

    
risposta data 25.06.2015 - 17:59
fonte
1

Penso che la maggior parte delle persone che rispondono a favorire la creazione di un nuovo oggetto manchi uno scenario critico: Garbage Collection (GC). GC può avere un vero successo in termini di prestazioni in applicazioni che creano molti oggetti (pensi giochi o applicazioni scientifiche).

Diciamo che ho un albero di espressioni che rappresenta un'espressione matematica, dove nei nodi interni ci sono nodi di funzione (ADD, SUB, MUL, DIV) e i nodi foglia sono nodi terminali (X, e, PI). Se la mia classe nodo ha un metodo Evaluate (), probabilmente chiamerà in modo ricorsivo i figli e raccoglierà dati tramite un nodo Dati. Per lo meno, tutti i nodi foglia dovranno creare un oggetto Data e quindi potranno essere riutilizzati lungo la strada fino alla valutazione del valore finale.

Ora diciamo che ho migliaia di questi alberi e li ho valutati in un ciclo. Tutti questi oggetti Data attiveranno un GC e causeranno un impatto sulle prestazioni, una grande (perdita di utilizzo della CPU fino al 40% per alcune esecuzioni nella mia app - Ho eseguito un profiler per ottenere i dati).

Una possibile soluzione? Riutilizzare quegli oggetti dati e basta richiamare alcuni .Reset () su di essi dopo aver finito di usarli. I nodi foglia non chiameranno più 'new Data ()', chiameranno un metodo Factory che gestisce il ciclo di vita dell'oggetto.

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

    
risposta data 15.12.2017 - 19:05
fonte

Leggi altre domande sui tag