2009-09-28 11 views
92

Quando è opportuno lanciare un'eccezione all'interno di un getter o setter di proprietà? Quando non è appropriato? Perché? I link a documenti esterni sull'argomento sarebbero utili ... Google si è rivelato sorprendentemente piccolo.Best practice: lancio di eccezioni dalle proprietà

+0

Vedi anche: http://stackoverflow.com/questions/633944/what-exception-to-throw-from-a-property-setter –

+0

Anche in relazione: http://stackoverflow.com/questions/ 1488322/what-exception-type-to-use-when-a-property-can not-be-null/1488346 # 1488346 –

+1

Ho letto entrambe le domande, ma nessuno dei due ha risposto a questa domanda completamente IMO. –

risposta

109

Microsoft ha le sue raccomandazioni su come progettare le proprietà a http://msdn.microsoft.com/en-us/library/ms229006.aspx

In sostanza, si consiglia di getter proprietà essere di accesso leggeri che sono sempre al sicuro chiamare. Raccomandano di ridisegnare i Getters come metodi se le eccezioni sono qualcosa che devi lanciare. Per i setter indicano che le eccezioni sono una strategia di gestione degli errori appropriata e accettabile.

Per gli indicizzatori, Microsoft indica che è accettabile che getter e setter generino eccezioni. E in effetti, molti indicizzatori nella libreria .NET lo fanno. L'eccezione più comune è ArgumentOutOfRangeException.

Ci sono alcune buone ragioni per le quali non si desidera generare eccezioni in getter di proprietà:

  • Poiché le proprietà "sembrano" essere i campi, non è sempre evidente che essi possono gettare una (da -design) eccezione; mentre con i metodi, i programmatori sono addestrati ad aspettarsi e indagare se le eccezioni sono una conseguenza prevista di invocare il metodo.
  • I getter sono utilizzati da molte infrastrutture .NET, come i serializzatori e il databinding (in WinForms e WPF per esempio) - occuparsi di eccezioni in tali contesti può rapidamente diventare problematico.
  • I getter di proprietà vengono automaticamente valutati dai debugger quando si guarda o si ispeziona un oggetto. Un'eccezione qui può essere fonte di confusione e rallentare gli sforzi di debug. È inoltre indesiderabile eseguire altre costose operazioni nelle proprietà (come l'accesso a un database) per gli stessi motivi.
  • Le proprietà sono spesso utilizzate in una convenzione di concatenazione: obj.PropA.AnotherProp.YetAnother - con questo tipo di sintassi diventa problematico decidere dove iniettare le dichiarazioni di cattura delle eccezioni.

Come nota a margine, si deve essere consapevoli che solo perché una proprietà viene non progettato un'eccezione, questo non significa che non; potrebbe facilmente chiamare il codice che lo fa. Anche il semplice atto di allocare un nuovo oggetto (come una stringa) potrebbe comportare delle eccezioni. Dovresti sempre scrivere il tuo codice in modo difensivo e aspettarti eccezioni da qualsiasi cosa tu invochi.

+0

Questo non vuol dire che le eccezioni non verranno mai lanciate, però. Considera che qualsiasi proprietà Get che alloca JIT ha il potenziale per lanciare a causa del costruttore. Il codice sarebbe davvero migliore se catturasse quell'eccezione silenziosamente? –

+37

Se si verifica un'eccezione irreversibile come "memoria insufficiente", non importa se si ottiene l'eccezione in una proprietà o altrove. Se non lo avevate nella proprietà, otterreste un paio di nanosecondi dopo la prossima cosa che alloca la memoria. La domanda non è "può una proprietà lanciare un'eccezione?" Quasi tutto il codice può generare un'eccezione a causa di una condizione fatale. La domanda è se una proprietà dovrebbe * design * gettare un'eccezione come parte del suo contratto specifico. –

+1

Non sono sicuro di aver compreso l'argomento in questa risposta. Per esempio, per quanto riguarda il databinding - sia WinForms sia WPF sono scritti _ in modo specifico per gestire correttamente le eccezioni generate dalle proprietà e per trattarli come errori di validazione - che è perfettamente valido (alcuni credono addirittura che sia il migliore) per fornire la convalida del modello di dominio . –

22

Non è quasi mai appropriato su un getter, e talvolta appropriato su un setter.

La migliore risorsa per questo tipo di domande è "Framework Design Guidelines" di Cwalina e Abrams; è disponibile come libro rilegato e grandi porzioni sono disponibili anche online.

Da paragrafo 5.2: dell'immobile in stile

evitare di gettare eccezioni getter proprietà. I getter di proprietà dovrebbero essere semplici operazioni e dovrebbero non avere precondizioni.Se un getter può lanciare un'eccezione, dovrebbe essere probabilmente ridisegnato per essere un metodo. Si noti che questa regola non si applica agli indicizzatori , dove ci si aspettano le eccezioni come risultato della convalida degli argomenti con gli argomenti .

Si noti che questa linea guida applica solo ai getter di proprietà. È OK gettare un'eccezione in un setter di proprietà.

+2

Mentre (in generale) sono d'accordo con tali linee guida, penso che sia utile fornire ulteriori informazioni sul perché dovrebbero essere seguite - e che tipo di conseguenze possono sorgere quando vengono ignorate. – LBushkin

+3

In che modo questo si riferisce agli oggetti usa e getta e alla guida che dovresti considerare di lanciare 'ObjectDisposedException' una volta che l'oggetto ha chiamato' Dispose() 'e qualcosa successivamente richiede un valore di proprietà? Sembra che la guida dovrebbe essere "evitare di lanciare eccezioni dai getter di proprietà, a meno che l'oggetto non sia stato eliminato, nel qual caso dovresti prendere in considerazione l'idea di lanciare un oggetto ObjectDisposedExcpetion". –

+4

Il design è l'arte e la scienza di trovare compromessi ragionevoli di fronte a requisiti contrastanti. In entrambi i casi sembra un ragionevole compromesso; Non sarei sorpreso di avere un oggetto disposto su una proprietà; né sarei sorpreso se non fosse così. Dal momento che l'utilizzo di un oggetto disposto è una pratica di programmazione terribile, non sarebbe saggio avere aspettative. –

1

Tutto questo è documentato in MSDN (come legata a in altre risposte), ma qui è una regola generale ...

Nel setter, se la vostra proprietà deve essere convalidato al di là tipo. Ad esempio, una proprietà denominata PhoneNumber dovrebbe probabilmente avere la convalida di espressioni regolari e dovrebbe generare un errore se il formato non è valido.

Per getter, probabilmente quando il valore è nullo, ma molto probabilmente è qualcosa che si desidera gestire sul codice chiamante (secondo le linee guida di progettazione).

31

Non c'è niente di sbagliato nel lanciare le eccezioni dai setter. Dopo tutto, quale modo migliore per indicare che il valore non è valido per una determinata proprietà?

Per i getter, è generalmente disapprovato e che può essere spiegato abbastanza facilmente: un getter di proprietà, in generale, segnala lo stato corrente di un oggetto; quindi, l'unico caso in cui è ragionevole che un getter lanci è quando lo stato non è valido. Ma generalmente è anche considerata una buona idea progettare le classi in modo tale che non sia possibile ottenere inizialmente un oggetto non valido, o metterlo in uno stato non valido con mezzi normali (cioè, assicurarsi sempre la completa inizializzazione nei costruttori, e prova a rendere i metodi eccezionalmente sicuri rispetto alla validità dello stato e agli invarianti di classe). Fintanto che continui a rispettare questa regola, i getter della tua proprietà non dovrebbero mai entrare in una situazione in cui devono segnalare uno stato non valido, e quindi non lanciare mai.

Esiste un'eccezione che conosco ed è in realtà piuttosto importante: qualsiasi oggetto che implementa IDisposable. Dispose è specificamente inteso come un modo per portare l'oggetto in uno stato non valido e c'è anche una classe di eccezione speciale, ObjectDisposedException, da utilizzare in quel caso. È perfettamente normale lanciare ObjectDisposedException da qualsiasi membro della classe, inclusi i getter di proprietà (escluso lo Dispose), dopo che l'oggetto è stato eliminato.

+3

Grazie Pavel. Questa risposta va al "perché" invece di affermare semplicemente che non è una buona idea lanciare un'eccezione dalle proprietà. – SolutionYogi

+1

Non mi piace l'idea che tutti i membri di un 'IDisposable' debbano essere resi inutilizzabili dopo un 'Dispose'. Se il richiamo di un membro richiederebbe l'uso di una risorsa che 'Dispose' ha reso non disponibile (ad esempio, il membro avrebbe letto i dati da un flusso che è stato chiuso) il membro dovrebbe lanciare' ObjectDisposedException' piuttosto che perdere, ad es. 'ArgumentException', ma se uno ha un modulo con proprietà che rappresentano i valori in determinati campi, sembrerebbe molto più utile consentire a tali proprietà di essere letti dopo lo smaltimento (restituendo gli ultimi valori digitati) piuttosto che richiedere ... – supercat

+1

. ..che 'Dispose' è differito fino a quando tutte queste proprietà sono state lette. In alcuni casi in cui un thread potrebbe utilizzare il blocco di letture su un oggetto mentre un altro lo chiude e dove i dati potrebbero arrivare in qualsiasi momento prima di 'Dispose', potrebbe essere utile avere' Dispose' tagliare i dati in entrata, ma consentire in precedenza- dati ricevuti da leggere. Non si dovrebbe forzare una distinzione artificiale tra 'Close' e' Dispose' in situazioni in cui nessuno avrebbe altrimenti bisogno di esistere. – supercat

1

Un approccio bello eccezioni è quella di usarli per documentare il codice per te stesso e gli altri sviluppatori come segue:

eccezioni dovrebbero essere per gli Stati programma eccezionale. Ciò significa che è bello scriverli dove vuoi!

Una ragione per cui si potrebbe desiderare di inserirli nei getter è documentare l'API di una classe - se il software genera un'eccezione non appena un programmatore tenta di utilizzarla in modo errato, non lo userà in modo errato! Ad esempio, se hai una convalida durante un processo di lettura dei dati, potrebbe non avere senso poter continuare e accedere ai risultati del processo se ci sono errori fatali nei dati. In questo caso potresti voler ottenere il lancio dell'output se ci sono errori per assicurare che un altro programmatore controlli questa condizione.

Sono un modo di documentare le ipotesi e i limiti di un sottosistema/metodo/qualsiasi cosa. Nel caso generale non dovrebbero essere catturati!Questo è anche perché non vengono mai lanciati se il sistema sta lavorando insieme nel modo previsto: se si verifica un'eccezione mostra che le ipotesi di un pezzo di codice non sono soddisfatte - per esempio non sta interagendo con il mondo circostante nel modo in cui era originariamente destinato a. Se si rileva un'eccezione scritta per questo scopo, probabilmente significa che il sistema è entrato in uno stato imprevedibile/incoerente - questo potrebbe portare a un arresto anomalo o corruzione di dati o simili che è probabilmente molto più difficile da rilevare/eseguire il debug.

I messaggi di eccezione sono un modo molto approssimativo di segnalare errori: non possono essere raccolti in massa e contengono solo una stringa. Ciò li rende inadatti a segnalare problemi nei dati di input. Nel normale funzionamento il sistema stesso non dovrebbe entrare in uno stato di errore. Di conseguenza, i messaggi in essi contenuti dovrebbero essere progettati per i programmatori e non per gli utenti: le cose che non sono corrette nei dati di input possono essere scoperte e inoltrate agli utenti in formati (personalizzati) più appropriati.

L'eccezione (haha!) A questa regola è simile all'IO dove le eccezioni non sono sotto il tuo controllo e non possono essere controllate in anticipo.

+2

In che modo questa risposta valida e pertinente è stata downvoted? Non ci dovrebbe essere alcuna politica in StackOverflow, e se questa risposta sembrava mancare il bullseye, aggiungere un commento a tale effetto. Il downvoting è per le risposte che sono irrilevanti o sbagliate. – debater

0

Questa è una domanda molto complessa e la risposta dipende da come viene utilizzato l'oggetto. Come regola generale, i getter di proprietà e i setter che "legano in ritardo" non devono generare eccezioni, mentre le proprietà con "binding anticipato" esclusivo devono generare eccezioni in caso di necessità. A proposito, lo strumento di analisi del codice di Microsoft sta definendo l'uso delle proprietà troppo ristretto a mio parere.

"rilegatura tardiva" indica che le proprietà si trovano attraverso la riflessione. Ad esempio, l'attributo Serializzabile è usato per serializzare/deserializzare un oggetto tramite le sue proprietà. Lanciare un'eccezione durante questo tipo di situazioni rompe le cose in modo catastrofico e non è un buon modo di usare le eccezioni per creare un codice più robusto

"associazione anticipata" significa che un uso di proprietà è associato nel codice dal compilatore.Ad esempio, quando un codice che scrivi fa riferimento a un getter di proprietà.In questo caso è OK generare eccezioni quando hanno senso

Un oggetto con attributi interni ha uno stato determinato dai valori di tali attributi.Le proprietà che esprimono gli attributi che sono consapevoli e sensibili allo stato interno dell'oggetto non devono essere utilizzate per l'associazione tardiva. Ad esempio, supponiamo di avere un oggetto che deve essereaperto, accessibile, quindi chiuso. In questo caso, l'accesso alle proprietà senza chiamare prima aperto dovrebbe comportare un'eccezione. Supponiamo, in questo caso, di non generare un'eccezione e di consentire al codice di accedere a un valore senza generare un'eccezione? Il codice sembrerà felice anche se ha un valore da un getter che non ha senso. Ora abbiamo messo il codice che ha chiamato il getter in una brutta situazione dato che deve sapere come controllare il valore per vedere se non ha senso. Ciò significa che il codice deve fare ipotesi sul valore che ha ottenuto dal getter della proprietà per convalidarlo. Ecco come viene scritto il codice errato.

0

Avevo questo codice in cui non ero sicuro di quale eccezione lanciare.

public Person 
{ 
    public string Name { get; set; } 
    public boolean HasPets { get; set; } 
} 

public void Foo(Person person) 
{ 
    if (person.Name == null) { 
     throw new Exception("Name of person is null."); 
     // I was unsure of which exception to throw here. 
    } 

    Console.WriteLine("Name is: " + person.Name); 
} 

I impedito il modello di avere la proprietà di essere nullo in primo luogo forzando come argomento nel costruttore.

public Person 
{ 
    public Person(string name) 
    { 
     if (name == null) { 
      throw new ArgumentNullException(nameof(name)); 
     } 
     Name = name; 
    } 

    public string Name { get; private set; } 
    public boolean HasPets { get; set; } 
} 

public void Foo(Person person) 
{ 
    Console.WriteLine("Name is: " + person.Name); 
}