è una cattiva pratica di programmazione restituire un nuovo oggetto in una proprietà

5

Ho sempre avuto questa domanda e penso che sia il momento di risolvere la risposta, è un grosso problema fare questo:

    public Rect Bounds
    {
        get 
        {
            var Top = (_targetPosition + _translationY) - _size / 2.0f;
            return new Rect(callOutStarMargin, Top, _size, _size); 
        }
    }

come alternativa a questo:

    private Rect _boundsRect = new Rect();

    public Rect Bounds
    {
        get 
        {
            var Top = (_targetPosition + _translationY) - _size / 2.0f;
            _boundsRect.Y = Top;
            return _boundsRect;
        }
    }

Facendo questo evito di creare un nuovo Rect ogni volta che viene invocata la proprietà, non sono sicuro che se facendo questo nel primo modo si creerebbe un eccesso di memoria / velocità in sovrappeso, se diciamo si accede a questa proprietà 1 o 2 volte su un elenco di 100 articoli al secondo.

Quindi quale scegliere?

Modifica

Penso di non essere chiaro qui, la mia vera domanda è Quanto è pesante rendere queste nuove istruzioni di oggetti semplici come Rect, Quanto è efficiente il garbage collector di C # per esempio in questo scenario di 100 nuovi rects al secondo, e la differenza del ciclo della cpu tra l'uno o l'altro modo

    
posta elios264 28.01.2014 - 23:18
fonte

7 risposte

4

Le proprietà sono zucchero sintattico per i campi approssimativi. Ci permettono di implementare vincoli o logica nella nostra classe preservando la barriera di astrazione di un POCO. La proprietà tipica in .NET non cambia ogni volta che la "osserviamo". La semantica delle proprietà automatiche è chiaramente definita per usare un campo di supporto generato dal compilatore, quindi le proprietà dovrebbero attenersi a quelle semantiche piuttosto che ridefinirle.

Creando una nuova istanza ogni chiamata getter è un mega-bug che aspetta di succedere.

È anche inutile spesa per realizzare nient'altro che un oggetto più piccolo. new () è sicuramente più costoso di un semplice test e ramo, soprattutto se è un nuovo tipo di riferimento sullo heap, quindi non è comunque un'ottimizzazione.

L'aspetto fuorviante dell'esempio è che questo approccio non è così pericoloso, perché parla di "immutabilità" e del fatto che di solito non modifichiamo le proprietà Bounds o altri campi struct.

Ci sono tre grossi problemi con questo approccio:

public Something Something
{
    get 
    {
        return new Something();   // bad - bug waiting to happen
    }
}
  1. Non è coerente con i campi reali o le proprietà .NET tipiche
  2. È una responsabilità, allocando inutilmente oggetti. Un semplice lambda su una raccolta di rettangoli genererebbe un abbandono patologico di GC per le raccolte di grandi dimensioni.
  3. È fuorviante e impossibile ragionare senza guardare l'implementazione. Non si dovrebbe mai cercare nell'implementazione di un intrinseco come un campo . Nessuno si aspetterà che abbiamo ridefinito la semantica di una proprietà pigra.

Non ci si aspetterebbe che quanto segue sia un ciclo infinito:

while(obj.Something.X < max)
    obj.Something.X++;

Per quanto riguarda le prestazioni e il GC, il punto non è se il GC sia efficiente. Il punto è che il GC non dovrebbe nemmeno essere in questa conversazione. Immagina che il tuo codice sia stato riutilizzato nel motore di ricerca di Bing. (Non un fan di Bing, solo riferimento a un sistema .NET ad alte prestazioni)

Se ce n'è bisogno, è meglio usare un metodo che indichi chiaramente che è quello che stai facendo.

    
risposta data 25.10.2014 - 11:38
fonte
9

Ciò che potresti voler ricordare è che i chiamanti possono presumere che il valore di una proprietà non cambia a meno che non sia stato assegnato esplicitamente. Questo non è necessariamente un presupposto intelligente, ma tuttavia è uno che sarà fatto.

Molti programmatori si aspettano il passaggio del seguente test:

var b1 = foo.Bounds;
var b2 = foo.Bounds;
Assert.AreEqual(b2, b1);

Ora, per qualcosa come un Rect , questo probabilmente non è un grosso problema, specialmente se è immutabile (come sarebbe normalmente un Rect ) e un tipo di valore o ha un operatore di uguaglianza di valore. In questo caso, non importa se due chiamate diverse ottengono due istanze diverse perché è molto difficile scrivere codice che implicitamente dipende dal fatto che siano la stessa istanza.

Inoltre, lazy instantiation è quasi sempre soddisfacente:

public Rect Bounds
{
    get
    {
        if (bounds == null)
            bounds = GetBounds(...);
        return bounds;
    }
}

... perché le successive chiamate avranno la stessa istanza, escludendo eventuali problemi di multi-threading.

Ma le cose possono diventare difficili quando una proprietà effettivamente restituisce un'istanza diversa ogni volta, e quell'istanza è un oggetto abbastanza complesso e particolarmente mutevole.

Ad esempio, supponiamo che tu restituisca un Stream :

public Stream OutputStream
{
    get { return new FileStream(...); }
}

Questo è abbastanza orribile perché è molto probabile che qualcuno scriva codice come:

foo.OutputStream.Write(data1, 0, data1.Length);
foo.OutputStream.Write(data2, 0, data2.Length);

E a seconda di come hai implementato OutputStream , accadrà una delle due cose brutte, o ci sarà un'eccezione di blocco file nella seconda chiamata, se OutputStream fa sempre riferimento allo stesso file, o il chiamante potrebbe scrivere involontariamente dei dati su due file diversi (se, diciamo, OutputStream crea effettivamente un nuovo file ogni volta), successivamente corrompendo entrambi i file.

Non aspettarti che i chiamanti salvino il valore di ritorno di una proprietà. Spesso non lo faranno. Quindi, se stai pensando di scrivere una proprietà che restituisce un oggetto diverso ogni volta, assicurati che non ci siano gravi rischi per far fluttuare due istanze nello stesso momento e possibilmente essere scambiate o perse.

Ho trascorso molte ore di debugging doloroso in cui sembrava che tutto funzionasse correttamente ma si è scoperto che stavo lavorando solo su una copia dell'istanza corretta. Gli iteratori con un'esecuzione differita sono un sottoinsieme particolarmente pernicioso di questo problema; non è insolito passare a foreach una volta sola e modificare alcune proprietà di ogni elemento, quindi foreach attraverso di essa e scoprire che tutto ha "reset"!

Un'ultima cosa: non si dovrebbe mai creare un oggetto che richiede la pulizia da un accessorio di proprietà. Non c'è quasi nessuna possibilità che il chiamante lo pulisca.

    
risposta data 29.01.2014 - 00:22
fonte
6

Non è raro, o cattivo, restituire nuovi oggetti dalle proprietà (dato che i tipi immutabili lo fanno sempre).

In definitiva, non è un problema finché non lo hai misurato e hai riscontrato che si tratta di un problema.

    
risposta data 28.01.2014 - 23:28
fonte
1

Ci sono tre cose che dovete considerare quando si decide se una proprietà deve inviare un nuovo oggetto o un riferimento a un oggetto mantenuto, e ognuno di questi varieranno per lingua e runtime.

  1. Che cosa eviterà errori inappropriati . Se passi un riferimento ad un oggetto privato, è teoricamente possibile che i consumatori a valle della tua classe possano avere un errore quando il tuo oggetto viene cancellato o aggiornato e hanno ancora un riferimento. (Se questo non è un problema sulla tua piattaforma , non preoccuparti.)

  2. Cosa eviterà prestazioni inaccettabili . Come sopra, può ottenere un successo in termini di prestazioni creando nuovi oggetti su ogni riferimento invece di far passare lo stesso oggetto. Questo può variare in modo sostanziale anche all'interno della stessa lingua:. Implementazioni Java primi avevano costoso oggetto di creazione, mentre moderno Java crea e distrugge gli oggetti con prestazioni di gran lunga migliore

  3. Quale sarà chi verrà dopo di che ci si aspetta. Se nessuno di quanto sopra sono un problema, si dovrebbe seguire tutto ciò che è tradizionalmente fatto nel vostro progetto. Sarebbe aggravante di aspettarsi una stored riferimento% oggetto% co_de del vostro oggetto di aggiornare quando non è così, o viceversa.

Personalmente, sono cresciuto su JavaScript, quindi preferisco riferimenti a oggetti che persistono da soli e primitive che non lo fanno. Ma sono solo io, e le lingue in cui faccio la maggior parte del mio lavoro ora hanno un comportamento considerevolmente diverso.

    
risposta data 29.01.2014 - 00:06
fonte
1

Il semplice test che suggerirei se dovrebbe essere accettabile per una proprietà (rispetto a un metodo) per un nuovo oggetto in una determinata situazione è se ci si aspetta che i chiamanti si preoccupino se ricevono un nuovo oggetto o un pre-esistente uno che è successo per avere le giuste caratteristiche. Se i chiamanti si aspetterebbero di ricevere un nuovo oggetto, il codice in questione dovrebbe essere un metodo. Se i chiamanti si aspetterebbero di ricevere un oggetto preesistente, il codice dovrebbe fornire uno. Il codice che può restituire un nuovo oggetto dovrebbe essere solo una proprietà se i chiamanti non si aspetteranno particolarmente di ricevere un nuovo oggetto, ma non gli dispiacerà se ne ricevono uno .

In generale, ci sono due situazioni in cui i chiamanti avranno tale non aspettativa:

  1. La prima chiamata creerà pigramente l'oggetto che verrà restituito da quella e da tutte le chiamate future, e nulla sull'oggetto indicherà quando è stato effettivamente creato . Inoltre, il codice dovrebbe garantire che non sia possibile che due chiamate possano restituire oggetti diversi, anche se vengono eseguite simultaneamente da thread diversi [usa il blocco o CompareExchange per assicurarti questo].

  2. L'oggetto che si sta costruendo è immutabile e ci si aspetta che i chiamanti si preoccupino solo dei dati in esso contenuti, piuttosto che della sua identità. Le stringhe sono un ottimo esempio di questo.

Per motivi di prestazioni, qualsiasi proprietà che restituisce oggetti che sono costosi da creare dovrebbe memorizzare nella cache l'oggetto creato più di recente per garantire che le chiamate ripetute consecutive che dovrebbero riportare lo stesso stato possano riutilizzare lo stesso oggetto; a volte può essere utile memorizzare nella cache oggetti aggiuntivi nei casi in cui lo stato di una proprietà potrebbe tornare a un valore precedente, ma il vantaggio in termini di prestazioni in situazioni diverse può variare da "peggio di niente" a "drastico". Tranne quando il caching non sarebbe pratico, tuttavia, la questione se utilizzare una proprietà o un metodo dovrebbe essere determinata principalmente dalla semantica.

    
risposta data 24.10.2014 - 23:19
fonte
0

Ricevo sempre nuovi oggetti. Soprattutto quando si lavora con le strutture, perché non posso inviarle tramite campi aggiuntivi. Quando stai restituendo un nuovo oggetto - la tua classe sembra più pulita, non ci sono campi aggiuntivi che nascondono alcuni dati. Tuttavia, quando rallenta la tua app, ti basta dare un profilo all'app e alle proprietà della cache che ti rallentano.

    
risposta data 18.03.2016 - 17:41
fonte
-1

Dovresti restituire un nuovo oggetto. Sempre.

Il sovraccarico è minimo in tutte le lingue moderne. Ma anche se non lo fosse, riutilizzare un oggetto statico esistente equivale a restituire il valore in una variabile globale. E questo porterà a COSE CATTIVE.

Anche se il programma è non threaded / concurrent, è molto fattibile che una funzione (o diverse funzioni in una catena di chiamate) vogliano esaminare i limiti di più rettangoli, logicamente allo stesso tempo. Se hai solo un oggetto globale, otterrai risposte molto sbagliate. Se il tuo programma è a tutti i thread multipli, le cose andranno ancora peggio. In entrambi i casi, gli errori accadranno e sarà difficile capire perché un semplice "quali sono i limiti di questo?" la chiamata non sembra avere un insieme di valori temporalmente stabile e coerente.

    
risposta data 25.10.2014 - 00:02
fonte

Leggi altre domande sui tag