Visual Basic .NET – La Gestione degli Errori di Federico BARBATI Generalità La gestione degli errori, è una parte fondamentale di un codice ben progettato. Fino ad oggi, gli errori nelle applicazioni scritte in Visual Basic venivano gestite con le istruzioni ON ERROR GOTO… e ON ERROR RESUME NEXT. Visual Basic .Net supporta le “vecchie” istruzioni tuttavia, questa gestione è considerata una cattiva abitudine dato che è stata introdotta una nuova gestione strutturata utilizzando l’istruzione: Try… Catch … Finally. Il problema legato alla gestione degli errori tradizionale è che non esiste una metodologia comune. Ci sono per esempio delle DLL che notificano un errore con uno 0 altre invece con un 1. Alcuni linguaggi di programmazione hanno dei propri meccanismi COM altri come C++, adottano il meccanismo delle eccezioni. Per ottenere l’interoperabilità tra i vari linguaggi, con il Framework .Net è stato introdotto un metodo unico per la gestione degli errori comune a tutti i linguaggi .Net: la generazione/intercettazione degli errori basato sulle eccezioni. Quando durante l’esecuzione di un’applicazione si verifica una condizione inaspettata (e quindi non gestita dallo sviluppatore), il codice solleva un’eccezione che altro codice dovrà intercettare. L’eccezione può essere intercettata dal codice della stessa procedura che ha sollevato l’errore. Se questo non accade, l’eccezione viene sollevata al chiamante e se quest’ultimo non l’intercetta, al chiamante del chiamante. Per cui l’eccezione risale fino a quando non viene intercettata da qualche procedura (logica top-down). Se non esiste nella nostra applicazione una procedura in grado di intercettare l’eccezione, verrà visualizzata in automatico una finestra di questo tipo: Questo vuol dire che con VB.Net, non ci sarà un’uscita brusca dal programma (con una probabile perdita di dati) come accade con VB6, ma si avrà la possibilità di ignorare l’errore e quindi continuare con l’esecuzione dell’applicazione. In effetti, quando viene sollevata un’eccezione, viene passata alla procedura che ha provocato l’errore, un oggetto Exception, il quale metterà a disposizione i suoi metodi e le sue proprietà . Tra le proprietà più utilizzate troviamo: • Message: Restituisce la descrizione dell’eccezione come Err.Description in VB6; • Source: Indica dove si verifica l’eccezione come Err.Source in VB6; • StackTrace: Partendo dal metodo che ha restituito l’eccezione, restituisce anche tutti i metodi chiamanti; • TargetSite: Restituisce nome e firma del metodo che ha sollevato l’eccezione. Il metodo ToString restituisce la descrizione dell’eccezione come verrebbe mostrata all’utente finale nel caso in cui non ci fosse una gestione da parte del codice. Oltre a mostrare il contenuto della proprietà Message, restituisce anche il nome del modulo sorgente. L’oggetto Exception, è definito all’interno del Framework .Net ed il suo namespace è: System.Exception, tuttavia le applicazioni sollevano gli errori ereditando dai namespace: • • System.SystemException (che eredita direttamente da System.Exception) System.ApplicationException. In definitiva, tutte le eccezioni definite nel Framework .Net ereditano da System.Exception mentre tutte le eccezioni personalizzate dall’utente all’interno di un’applicazione, ereditano da System.ApplicationException. Per esempio il namespace System.IO può sollevare un’eccezione System.DirectoryNotFoundException (directory non trovata) o System.FileNotFoundException (File non trovato), mentre una divisione può sollevare l’eccezione System.DivideByZeroException (divisione per 0) che a loro volta ereditano direttamente da System.Exception (l’elenco completo è contenuto nella documentazione originale dell’SDK installata con Visual Studio .Net). L’Istruzione Try…Catch…Finally Quando si esegue un codice che potrebbe sollevare un’eccezione, è bene inserirlo in un costrutto Try..End Try. Ad esempio: Dim Divisione, Valore1, Valore2 As Integer Try Valore1 = 2 Valore2 = 0 Divisione = Valore1 \ Valore2 MessageBox.Show(Divisione) Catch provaEx As Exception MessageBox.Show(provaEx.Message) End Try Da notare che le tre variabili verranno dichiarate tutte Integer mentre in VB6 le prime due erano Variant e solo la terza (Valore2) era Integer. Inoltre “\” effettua una divisione tra interi mentre “/” effettua una divisione in virgola mobile, Se il codice tra Try e Catch provoca un errore, Visual Basic, passerà il controllo al blocco Catch, inversamente ignorerà tutto quello dopo il Catch. In questo caso verrà restituito alla procedura, un oggetto provaEx di tipo System.Exception e mediante un MessageBox verrà visualizzata la proprietà provaEx.Message la quale restituisce la descrizione dell’eccezione. In quest’ultimo caso, non abbiamo sfruttato la flessibilità dell’istruzione dato che il filtro del blocco Catch è impostato sul generico tipo Exception. Per questo motivo, per ottenere un controllo più selettivo, useremo più blocchi Catch ognuno dei quali verifica un diverso tipo Exception. Dim Divisione, Valore1, Valore2 As Integer Try Valore1 = 2 Valore2 = 0 Divisione = Valore1 \ Valore2 MessageBox.Show(Divisione) Catch provaEx As DivideByZeroException MessageBox.Show("Divisione per 0!", "Eccezione:") Catch provaEx As OverflowException MessageBox.Show("Overflow!", "Eccezione:") Catch provaEx As Exception MessageBox.Show("Altra!", "Eccezione:") End Try Visual Basic confronta l’eccezione sollevata con le espressioni contenute nelle clausole Catch nell’ordine in cui sono scritte. In questo caso siamo in grado di intercettare una errore di divisione per 0, oppure un overflow e di conseguenza inserire il codice per la gestione più adatto. Ad esempio, quando si verifica un’eccezione DivideByZeroException, potremmo richiedere all’utente un nuovo valore per la variabile Valore2. Tuttavia è bene inserire alla fine un blocco Catch generico in grado di intercettare tutti i tipi di eccezione. E’ possibile uscire da un Try…End Try invocando l’istruzione Exit Try che può apparire anche nei blocchi Catch. Il runtime del .Net definisce automaticamente alcune eccezioni che si verificano in condizioni non dipendenti dal programma ma dal sistema (StackOverflowException, OutOfMemoryException,). La Parola Chiave WHEN La parola chiave When (non gestita in C#) permette di specificare un’ulteriore condizione da soddisfare al filtro impostato in un blocco Catch. In questo esempio, utilizzeremo più blocchi Catch con filtro identico ma con condizione When diversa: Dim Divisione, Valore1, Valore2 As Integer Try Valore1 = 10 Valore2 = 0 Divisione = Valore1 \ Valore2 MessageBox.Show(Divisione) Catch provaEx As DivideByZeroException When Valore1 < 10 MessageBox.Show("Divisione per 0 per valore1 < 10!", "Eccezione:") Catch provaEx As DivideByZeroException When Valore1 >= 10 MessageBox.Show("Divisione per 0 per valore1 >= 10!", "Eccezione:") Catch provaEx As OverflowException MessageBox.Show("Overflow!", "Eccezione:") Catch provaEx As Exception MessageBox.Show("Altra!", "Eccezione:") End Try Con la gestione dell’esempio, verrà eseguito il secondo blocco Catch dato che Valore1 = 10. E’ comunque da tenere in considerazione che si può arrivare allo stesso risultato usando il costrutto If..Then..Else perdendo comunque leggibilità e organizzazione. La parola chiave Finally Quando viene sollevato un errore, spesso si ha la necessità di utilizzare un codice di cleanup ad esempio chiudere un file o una connessione ad un database. La parola chiave Finally è utilizzata in questi casi. Tutto il codice inserito tra Finally ed End Try verrà eseguito indipendentemente dal fatto che sia stata sollevata un’eccezione o meno. Dim Divisione, Valore1, Valore2 As Integer Try Valore1 = 10 Valore2 = 0 Divisione = Valore1 \ Valore2 MessageBox.Show(Divisione) Catch provaEx As DivideByZeroException When Valore1 < 10 MessageBox.Show("Divisione per 0 per valore1 < 10!", "Eccezione:") Catch provaEx As DivideByZeroException When Valore1 >= 10 MessageBox.Show("Divisione per 0 per valore1 >= 10!", "Eccezione:") Catch provaEx As OverflowException MessageBox.Show("Overflow!", "Eccezione:") Catch provaEx As Exception MessageBox.Show("Altra!", "Eccezione:") Finally MessageBox.Show("Il valore1=" & Str(Valore1) & " ed il Valore2=" & Str(Valore2)) End Try Con questo codice apparirà una MessageBox che visualizza i valori delle variabili Valore1 e Valore2 indipendentemente dal fatto che venga sollevata un’eccezione. Il codice dopo Finally verrà eseguito perfino se si utilizza un Exit Try. Nel menu Debug di Visual Studio .Net, ciccando su Eccezioni, verrà visualizzata la finestra di dialogo Eccezioni. Questa finestra di dialogo, mostra tutti i tipi di eccezioni gestite dal runtime del .Net raggruppate per Namespace per cui può essere utile per avere una visione più ampia delle vari tipi. Selezionando un nodo oppure una singola eccezione, si può decidere, (nel caso in cui l’applicazione intercetti l’eccezione selezionata) se continuare e ignorare l’errore oppure passare il controllo al debugger . E’ possibile infine sollevare un’eccezione utilizzando Throw (che corrisponde a Err.Raise in VB6). Ad esempio, potrei decidere di sollevare un’eccezione se la variabile Valore2 è minore o uguale a zero: Dim Divisione, Valore1, Valore2 As Integer Try Valore1 = 10 Valore2 = 0 If Valore2 <= 0 Then Throw New DivideByZeroException("Valore2 deve essere > di 0") End If Divisione = Valore1 \ Valore2 MessageBox.Show(Divisione) Catch provaEx As Exception MessageBox.Show(provaEx.Message) End Try Quando il valore della variabile Valore2 = 0, il codice solleva un’eccezione DivideByZeroException e tra parentesi personalizza il messaggio che verrà restituito. E’ possibile utilizzare Throw anche all’interno di un blocco Catch per notificare al chiamante l’eccezione: Dim Divisione, Valore1, Valore2 As Integer Try Valore1 = 10 Valore2 = 0 Divisione = Valore1 \ Valore2 MessageBox.Show(Divisione) Catch provaEx As Exception MessageBox.Show("Altra!", "Eccezione:") Throw End Try Nell’esempio, se una procedura chiama la funzione Divisione, alla procedura chiamante, verrà restituita l’eccezione solo perchè gli viene notificata tramite Throw. Concludo consigliando vivamente di utilizzare questa novità di VB.Net a scapito di On Error... perchè l’esecuzione sarà più veloce e consente l’interoperabilità con altri linguaggi (C#) che non supportano la vecchia gestione VB6. N.B. Per dubbi e chiarimenti scrivere a [email protected]