Procedure consigliate: lancio di eccezioni dalle proprietà


111

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.




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

Ogni volta che è necessario. Sia le domande che le risposte precedenti mostrano che è consentito e apprezzato sollevare eccezioni da getter o setter, quindi puoi semplicemente "essere intelligente".
Lex Li,

Risposte:


135

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à:

  • Poiché le proprietà "sembrano" essere campi, non è sempre evidente che possono generare un'eccezione (di progettazione); mentre con i metodi, i programmatori sono addestrati ad aspettarsi e indagare se le eccezioni sono una conseguenza attesa dell'invocazione del metodo.
  • I getter sono utilizzati da molte infrastrutture .NET, come serializzatori e associazioni di dati (in WinForms e WPF, ad esempio): gestire le eccezioni in tali contesti può diventare rapidamente problematico.
  • I getter di proprietà vengono valutati automaticamente dai debugger quando si guarda o si ispeziona un oggetto. Un'eccezione qui può creare confusione e rallentare i tuoi sforzi di debug. È anche indesiderabile eseguire altre operazioni costose nelle proprietà (come l'accesso a un database) per gli stessi motivi.
  • Le proprietà sono spesso utilizzate in una convenzione di concatenamento: 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.


41
Se ti imbatti in un'eccezione fatale come "memoria esaurita", non importa se ottieni l'eccezione in una proprietà o da qualche altra parte. Se non l'hai inserito nella proprietà, lo avresti ricevuto solo un paio di nanosecondi dopo dalla prossima cosa che alloca memoria. La domanda non è "una proprietà può generare un'eccezione?" Quasi tutto il codice può generare un'eccezione a causa di una condizione fatale. La domanda è se una proprietà debba in base alla progettazione generare un'eccezione come parte del suo contratto specifico.
Eric Lippert,

1
Non sono sicuro di aver capito l'argomento in questa risposta. Ad esempio, per quanto riguarda l'associazione di dati - sia WinForms che WPF sono scritti specificamente per gestire correttamente le eccezioni generate dalle proprietà e per trattarle come errori di convalida - che è un modo perfettamente corretto (alcuni credono addirittura che sia il migliore) per fornire la convalida del modello di dominio .
Pavel Minaev,

6
@Pavel - sebbene sia WinForms che WPF possano recuperare con grazia dalle eccezioni nelle funzioni di accesso alle proprietà, non è sempre facile identificare e ripristinare tali errori. In alcuni casi, (come quando in WPF un setter di modelli di controllo genera un'eccezione) l'eccezione viene inghiottita silenziosamente. Questo può portare a dolorose sessioni di debug se non ti sei mai imbattuto in tali casi prima.
LBushkin,

1
@Steven: Allora quanto ti è stata utile quella lezione nel caso eccezionale? Se poi hai dovuto scrivere codice difensivo per gestire tutte quelle eccezioni a causa di un errore e presumibilmente fornire impostazioni predefinite adeguate, perché non fornire quelle impostazioni predefinite nel tuo catch? In alternativa, se le eccezioni di proprietà vengono lanciate all'utente, perché non lanciare semplicemente l'originale "InvalidArgumentException" o simile in modo che possa fornire il file delle impostazioni mancante?
Zhaph - Ben Duguid,

6
C'è un motivo per cui queste sono linee guida e non regole; nessuna linea guida copre tutti i casi limite pazzi. Probabilmente avrei creato questi metodi e non le proprietà da solo, ma è un giudizio.
Eric Lippert,

34

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 ObjectDisposedExceptionda qualsiasi membro della classe, inclusi i getter di proprietà (e escludendo Disposese stesso), dopo che l'oggetto è stato eliminato.


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

1
Non mi piace l'idea che assolutamente tutti i membri di un IDisposabledovrebbero essere resi inutili dopo a Dispose. Se invocare un membro richiederebbe l'uso di una risorsa che Disposeha reso non disponibile (ad esempio il membro leggerebbe i dati da un flusso che è stato chiuso) il membro dovrebbe lanciare ObjectDisposedExceptionpiuttosto 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 ...
supercat

1
... che Disposesarà 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 Disposetagliare i dati in arrivo, ma consentire la lettura dei dati ricevuti in precedenza. Non si dovrebbe forzare una distinzione artificiale tra Closee Disposein situazioni in cui altrimenti non dovrebbe esistere nessuno.
supercat

Comprendere il motivo della regola ti consente di sapere quando infrangere la regola (Raymond Chen). In questo caso, possiamo vedere che se c'è un errore irrecuperabile di qualsiasi tipo non dovresti nasconderlo nel getter, poiché in questi casi l'applicazione deve uscire al più presto.
Ben

Il punto che stavo cercando di sottolineare è che i getter di proprietà in genere non dovrebbero contenere logica che consentirebbe errori irreversibili. Se lo fa, potrebbe essere meglio Get...invece come metodo. Un'eccezione qui è quando devi implementare un'interfaccia esistente che richiede di fornire una proprietà.
Pavel Minaev

24

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à.


2
Anche se (in generale) sono d'accordo con tali linee guida, penso sia utile fornire alcune informazioni aggiuntive sul motivo per cui dovrebbero essere seguite e sul tipo di conseguenze che possono sorgere quando vengono ignorate.
LBushkin,

3
In che modo questo si collega agli oggetti usa e getta e alla guida che dovresti considerare di lanciare ObjectDisposedExceptionuna 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".
Scott Dorman,

4
Il design è l'arte e la scienza di trovare ragionevoli compromessi di fronte a requisiti contrastanti. In entrambi i casi sembra un ragionevole compromesso; Non sarei sorpreso di avere un oggetto smaltito lanciato su una proprietà; né sarei sorpreso se non lo facesse. Poiché l'utilizzo di un oggetto disposto è una terribile pratica di programmazione, non sarebbe saggio avere aspettative.
Eric Lippert,

1
Un altro scenario in cui è totalmente valido generare eccezioni dall'interno di getter è quando un oggetto utilizza invarianti di classe per convalidare il suo stato interno, che deve essere controllato ogni volta che viene effettuato un accesso pubblico, indipendentemente dal fatto che si tratti di un metodo o di una proprietà
Trap

2

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.


2
In che modo questa risposta valida e pertinente è stata sottovalutata? Non dovrebbe esserci alcuna politica in StackOverflow, e se questa risposta sembra mancare il bersaglio, aggiungi un commento in tal senso. Il downvoting è per le risposte irrilevanti o sbagliate.
dibattitore

1

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).



0

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.


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);
}

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);
}
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.