Quando è appropriato lanciare un'eccezione dall'interno di una proprietà getter o setter? Quando non è appropriato? Perché? I collegamenti a documenti esterni sull'argomento sarebbero utili ... Google ha scoperto sorprendentemente poco.
Quando è appropriato lanciare un'eccezione dall'interno di una proprietà getter o setter? Quando non è appropriato? Perché? I collegamenti a documenti esterni sull'argomento sarebbero utili ... Google ha scoperto sorprendentemente poco.
Risposte:
Microsoft ha i suoi consigli su come progettare le proprietà su http://msdn.microsoft.com/en-us/library/ms229006.aspx
In sostanza, raccomandano che i getter di proprietà siano funzioni di accesso leggere che sono sempre sicure da chiamare. Raccomandano di riprogettare i getter 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 sia i getter che i setter generino eccezioni. E in effetti, molti indicizzatori nella libreria .NET lo fanno. L'eccezione più comune è ArgumentOutOfRangeException
.
Ci sono alcuni buoni motivi per cui non vuoi lanciare eccezioni nei getter di proprietà:
obj.PropA.AnotherProp.YetAnother
- con questo tipo di sintassi diventa problematico decidere dove iniettare le istruzioni di cattura delle eccezioni.Come nota a margine, si dovrebbe essere consapevoli che solo perché una proprietà non è progettata per generare un'eccezione, ciò non significa che non lo farà; potrebbe facilmente essere un codice di chiamata 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 tutto ciò che invochi.
Non c'è niente di sbagliato nel lanciare eccezioni dai setter. Dopo tutto, quale modo migliore per indicare che il valore non è valido per una data proprietà?
Per i getter, è generalmente disapprovato e questo può essere spiegato abbastanza facilmente: un getter di proprietà, in generale, riporta lo stato corrente di un oggetto; quindi, l'unico caso in cui è ragionevole che un getter lanci è quando lo stato non è valido. Ma è anche generalmente considerata una buona idea progettare le classi in modo tale che semplicemente non sia possibile ottenere inizialmente un oggetto non valido, o metterlo in uno stato non valido tramite mezzi normali (cioè, garantire sempre l'inizializzazione completa nei costruttori, e provare a rendere i metodi sicuri rispetto alle eccezioni rispetto alla validità dello stato e alle invarianti di classe). Fintanto che ti attieni a questa regola, i getter di proprietà non dovrebbero mai trovarsi in una situazione in cui devono segnalare uno stato non valido, e quindi non lanciare mai.
C'è un'eccezione che conosco, ed è in realtà piuttosto importante: qualsiasi oggetto implementato IDisposable
. Dispose
è specificamente inteso come un modo per portare un oggetto in uno stato non valido, e c'è anche una speciale classe di eccezione ObjectDisposedException
, da usare in quel caso. È perfettamente normale eseguire il lancio ObjectDisposedException
da qualsiasi membro della classe, inclusi i getter di proprietà (e escludendo Dispose
se stesso), dopo che l'oggetto è stato eliminato.
IDisposable
dovrebbero essere resi inutili dopo a Dispose
. Se invocare un membro richiederebbe l'uso di una risorsa che Dispose
ha reso non disponibile (ad esempio il membro leggerebbe i dati da un flusso che è stato chiuso) il membro dovrebbe lanciare ObjectDisposedException
piuttosto che perdere ad esempio ArgumentException
, ma se uno ha un form con proprietà che rappresentano il valori in determinati campi, sembrerebbe molto più utile consentire a tali proprietà di essere lette dopo lo smaltimento (restituendo gli ultimi valori digitati) piuttosto che richiedere ...
Dispose
sarà rimandato fino a quando tutte queste proprietà saranno state lette. In alcuni casi in cui un thread potrebbe utilizzare il blocco delle letture su un oggetto mentre un altro lo chiude e in cui i dati potrebbero arrivare in qualsiasi momento prima Dispose
, potrebbe essere utile Dispose
tagliare i dati in arrivo, ma consentire la lettura dei dati ricevuti in precedenza. Non si dovrebbe forzare una distinzione artificiale tra Close
e Dispose
in situazioni in cui altrimenti non dovrebbe esistere nessuno.
Get...
invece come metodo. Un'eccezione qui è quando devi implementare un'interfaccia esistente che richiede di fornire una proprietà.
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 di esso sono disponibili anche online.
Dalla sezione 5.2: Progettazione della proprietà
EVITARE di lanciare eccezioni dai getter di proprietà. I getter di proprietà dovrebbero essere operazioni semplici e non dovrebbero avere precondizioni. Se un getter può lanciare un'eccezione, probabilmente dovrebbe essere riprogettato per essere un metodo. Nota che questa regola non si applica agli indicizzatori, dove ci aspettiamo eccezioni come risultato della convalida degli argomenti.
Si noti che questa linea guida si applica solo ai getter di proprietà. Va bene lanciare un'eccezione in un setter di proprietà.
ObjectDisposedException
una volta che l'oggetto è stato Dispose()
chiamato 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 considerare di lanciare un ObjectDisposedExcpetion".
Un buon approccio alle eccezioni è usarle per documentare il codice per te e per altri sviluppatori come segue:
Le eccezioni dovrebbero essere per gli stati del programma eccezionali. Ciò significa che va bene scriverli dove vuoi!
Uno dei motivi per cui potresti volerli inserire in getter è per documentare l'API di una classe: se il software lancia un'eccezione non appena un programmatore cerca di usarlo in modo sbagliato, non lo userà in modo sbagliato! Ad esempio, se si dispone della convalida durante un processo di lettura dei dati, potrebbe non avere senso essere in grado di continuare e accedere ai risultati del processo se ci sono stati errori fatali nei dati. In questo caso potresti voler fare in modo che l'output lanci se ci sono stati errori per assicurarti che un altro programmatore controlli questa condizione.
Sono un modo per documentare le ipotesi e i confini di un sottosistema / metodo / qualunque cosa. Nel caso generale non dovrebbero essere catturati! Questo anche perché non vengono mai lanciati se il sistema funziona insieme nel modo previsto: se si verifica un'eccezione, mostra che le ipotesi di un pezzo di codice non sono soddisfatte, ad esempio non sta interagendo con il mondo che lo circonda nel modo originariamente era previsto. Se si rileva un'eccezione scritta per questo scopo, probabilmente significa che il sistema è entrato in uno stato imprevedibile / incoerente: ciò potrebbe alla fine portare a un arresto anomalo o alla corruzione dei dati o simili che è probabile che sia molto più difficile da rilevare / eseguire il debug.
I messaggi di eccezione sono un modo molto grossolano 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. Durante il 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 sono sbagliate nei dati di input possono essere scoperte e inoltrate agli utenti in formati più adatti (personalizzati).
L'eccezione (haha!) A questa regola è cose come IO in cui le eccezioni non sono sotto il tuo controllo e non possono essere verificate in anticipo.
Tutto questo è documentato in MSDN (come collegato in altre risposte) ma qui c'è una regola generale ...
Nel setter, se la tua proprietà deve essere convalidata sopra e oltre il tipo. Ad esempio, una proprietà chiamata PhoneNumber dovrebbe probabilmente avere la convalida dell'espressione regolare e dovrebbe generare un errore se il formato non è valido.
Per i getter, possibilmente quando il valore è nullo, ma molto probabilmente è qualcosa che vorrai gestire sul codice chiamante (secondo le linee guida di progettazione).
MSDN: acquisizione e generazione di tipi di eccezione standard
Questa è una domanda molto complessa e la risposta dipende da come viene utilizzato il tuo oggetto. Come regola pratica, i getter di proprietà e i setter che sono "late binding" non dovrebbero generare eccezioni, mentre le proprietà con esclusivamente "early binding" dovrebbero lanciare eccezioni quando se ne presenta la necessità. A proposito, lo strumento di analisi del codice di Microsoft sta definendo l'uso delle proprietà in modo troppo restrittivo secondo me.
"associazione tardiva" significa che le proprietà si trovano attraverso la riflessione. Ad esempio, l'attributo Serializeable "viene utilizzato per serializzare / deserializzare un oggetto tramite le sue proprietà. Lanciare un'eccezione durante in questo tipo di situazione rompe le cose in modo catastrofico e non è un buon modo per usare le eccezioni per rendere il codice più robusto.
"associazione anticipata" significa che l'uso di una proprietà è vincolato nel codice dal compilatore. Ad esempio, quando un codice che scrivi fa riferimento a una proprietà getter. In questo caso va bene lanciare eccezioni quando hanno senso.
Un oggetto con attributi interni ha uno stato determinato dai valori di quegli attributi. Le proprietà che esprimono attributi consapevoli e sensibili allo stato interno dell'oggetto non devono essere utilizzate per l'associazione tardiva. Ad esempio, supponiamo che tu abbia un oggetto che deve essere aperto, accessibile e quindi chiuso. In questo caso, l'accesso alle proprietà senza prima chiamare open dovrebbe comportare un'eccezione. Supponiamo, in questo caso, di non lanciare un'eccezione e di consentire al codice di accedere a un valore senza generare un'eccezione? Il codice sembrerà felice anche se ha ottenuto un valore da un getter che non ha senso. Ora abbiamo messo il codice che ha chiamato il getter in una brutta situazione poiché 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. Questo è il modo in cui viene scritto un codice errato.
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);
}
Ho impedito al modello di avere la proprietà null in primo luogo forzandolo 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);
}