Le proprietà dovrebbero avere effetti collaterali


19

Le proprietà in C # dovrebbero avere effetti collaterali oltre alla notifica di un cambiamento nei suoi stati?

Ho visto proprietà utilizzate in diversi modi. Dalle proprietà che caricheranno il valore la prima volta che accederanno alle proprietà che hanno enormi effetti collaterali come causare un reindirizzamento a una pagina diversa.



1
Tra i risultati di @ Job, perché dovrei evitare di usare Proprietà in C #? l'opinione di aka Jeffrey Richter è molto rilevante per questa domanda.
rwong

@rwong Rilevante sì ma completamente privo di senso - tuttavia deve essere letto (in modo da essere consapevole dei problemi che evidenzia). Almeno su questo argomento sono nel campo degli Skeet.
Apoorv Khurasia,

Anche se questo riguarda i costruttori, anche questo è rilevante: programmers.stackexchange.com/a/164255/1996
Jim G.

Risposte:


40

Presumo che tu stia parlando di proprietà di sola lettura , o almeno di getter di proprietà , dal momento che un setter di proprietà avrà, in quasi tutti i casi, effetti collaterali. Altrimenti non è molto utile.

In generale, un buon design segue il principio della minima sorpresa . Non fare cose che i chiamanti non si aspettano che tu faccia, soprattutto se quelle cose potrebbero cambiare in futuro risultati .

In generale , ciò significa che i getter di proprietà non dovrebbero avere effetti collaterali.

Tuttavia , facciamo attenzione a cosa intendiamo per "effetto collaterale".

Un effetto collaterale è, tecnicamente, qualsiasi modifica di stato. Potrebbe essere uno stato accessibile al pubblico o ... potrebbe essere uno stato totalmente privato.

Caricatori pigri / differiti sono un esempio di stato che è quasi esclusivamente privato. Finché non è responsabilità del chiamante liberare quella risorsa, allora si sta effettivamente riducendo la sorpresa e la complessità in generale usando l'inizializzazione differita. Un chiamante normalmente non prevede di dover segnalare esplicitamente l'inizializzazione di una struttura interna . Pertanto, l'inizializzazione lenta non viola il principio di cui sopra.

Un altro esempio è una proprietà sincronizzata. Affinché un metodo sia thread-safe, spesso dovrà essere protetto da sezioni critiche o mutex. Entrare in una sezione critica o acquisire un mutex è un effetto collaterale; stai modificando lo stato, generalmente lo stato globale . Tuttavia, questo effetto collaterale è necessario per prevenire un peggio tipo di sorpresa : la sorpresa dei dati che vengono modificati (o peggio, parzialmente modificati) da un altro thread.

Quindi allenterei un po 'la restrizione a quanto segue: Le letture delle proprietà non dovrebbero avere effetti collaterali visibili o effetti collaterali che cambiano la loro semantica .

Se non vi è alcun modo per cui un chiamante possa mai essere influenzato o addirittura informato di un effetto collaterale, tale effetto collaterale non sta causando alcun danno. Se per te fosse impossibile scrivere un test per verificare l'esistenza di un particolare effetto collaterale, è abbastanza localizzato da etichettarlo come un dettaglio dell'implementazione privata, e quindi senza alcuna preoccupazione legittima per il mondo esterno.

Ma stai attento; come regola generale, dovresti cercare di evitare effetti collaterali, perché spesso quello che potresti pensare di essere un dettaglio dell'implementazione privata può inaspettatamente fuoriuscire e diventare pubblico.


2
+1 - una bella risposta completa che include i casi più complicati che trasformano un "deve" in un "dovrebbe ... tranne quando ...."
quick_now

Un altro esempio di un effetto collaterale accettabile su un getter: una funzione di registrazione.
Loren Pechtel,

5

Risponderò alla tua domanda con una domanda: cosa succede quando modifichi la proprietà Width di un modulo?

Appena al di sopra della mia testa, questo farà:

  • Modifica il valore del campo di supporto.
  • Attiva un evento.
  • Modificare le dimensioni del modulo, segnalare la modifica al gestore delle finestre e richiedere una riverniciatura.
  • Avvisare i controlli sul modulo della modifica in modo che chiunque di essi debba cambiare dimensione o posizione quando il modulo viene ridimensionato può farlo.
  • Causa tutti i controlli che sono cambiati in eventi di fuoco e dire al gestore di finestre che devono essere ridisegnati.
  • Probabilmente anche altre cose.

(Dichiarazione di non responsabilità: sono uno sviluppatore Delphi, non uno sviluppatore .NET. Questo potrebbe non essere accurato al 100% per C #, ma scommetto che è abbastanza vicino, soprattutto considerando quanto del framework .NET originale era basato su Delphi.)

Tutti questi "effetti collaterali" si verificano quando si modifica la proprietà Width in un modulo. Qualcuno di loro sembra inappropriato o errato in qualche modo?


purché non entri in un ciclo infinito che si chiama. Per evitare ciò, ogni "modifica" verrà associata ad almeno tre eventi: uno attivato prima della modifica, uno attivato durante la modifica e uno attivato al termine.
rwong

5

Dai un'occhiata a Microsoft Design Rules , in particolare uno di questi:

CA1024: utilizzare le proprietà ove appropriato

... Un metodo è un buon candidato per diventare una proprietà se è presente una di queste condizioni:

  • Non accetta argomenti e restituisce le informazioni sullo stato di un oggetto.
  • Accetta un singolo argomento per impostare una parte dello stato di un oggetto.

Le proprietà dovrebbero comportarsi come se fossero campi; se il metodo non può, non dovrebbe essere modificato in una proprietà. I metodi sono migliori delle proprietà nelle seguenti situazioni:

  • Il metodo esegue un'operazione che richiede tempo. Il metodo è sensibilmente più lento del tempo necessario per impostare o ottenere il valore di un campo.
  • Il metodo esegue una conversione. L'accesso a un campo non restituisce una versione convertita dei dati archiviati.
  • Il metodo Get ha un effetto collaterale osservabile. Il recupero del valore di un campo non produce effetti collaterali.
  • L'ordine di esecuzione è importante. L'impostazione del valore di un campo non dipende dall'occorrenza di altre operazioni.
  • Chiamare il metodo due volte in successione crea risultati diversi.
  • Il metodo è statico ma restituisce un oggetto che può essere modificato dal chiamante. Il recupero del valore di un campo non consente al chiamante di modificare i dati archiviati dal campo.
  • Il metodo restituisce un array ...

2

Credo che le proprietà non dovrebbero avere effetti collaterali. Esiste tuttavia un'eccezione a questa regola: il caricamento lento. Se vuoi caricare lentamente qualcosa in una proprietà, va bene, perché il client non può dire che il caricamento lento sta andando, e in genere non importa.

EDIT : Oh, un'altra cosa - sparare un evento dicendo "PropertyXYZ Changed!" (es. INotifyPropertyChanged) - va bene per i setter.


2

Oltre al caricamento lento menzionato in precedenza, esiste anche il caso delle funzioni di registrazione. Questi sarebbero rari ma non fuori discussione.


2

Non ci sono quasi assoluti nella programmazione, per rispondere alla tua domanda dobbiamo guardare alcune proprietà desiderabili degli oggetti e vedere se questo è qualcosa da applicare al nostro caso

  1. Vogliamo che le classi siano facilmente testabili.
  2. Vogliamo che le classi supportino la concorrenza
  3. Vogliamo che le lezioni siano facili da ragionare per terze parti

Se rispondiamo di si ad alcune di queste domande, probabilmente è una buona idea pensare molto attentamente alle proprietà e ai loro effetti collaterali. Suppongo che quando dici effetti collaterali intendi effetti collaterali secondari poiché l'aggiornamento del valore è un effetto collaterale in sé.

Più effetti collaterali in generale, più difficile sarà una classe da testare. Più effetti secondari secondari sarà la concorrenza più difficile da gestire, ma questo è un problema difficile che probabilmente richiede alcuni importanti progetti di progettazione.

Il grande gotcha è probabilmente per le persone che trattano la tua classe come una "scatola nera", non sarebbe prontamente disponibile che alcuni stati siano cambiati solo perché hanno letto una proprietà o che alcune altre proprietà cambiano perché un'altra modifica (che potrebbe portare agli aggiornamenti a cascata).

Quindi in generale no, vogliamo che le proprietà siano il più facili da ragionare e il più semplice possibile, ciò è implicito nella decisione di progettazione, altrimenti sarebbe un metodo. Un buon programmatore può sempre infrangere le regole, ma assicurati di avere davvero un buon motivo :)

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.