Esercizio - Intercettare tipi di eccezione specifici

Completato

In precedenza in questo modulo si è appreso che gli oggetti eccezione rilevati dall'applicazione C# sono istanze di una classe di eccezione. In generale, il codice sarà catch uno dei seguenti:

  • Oggetto eccezione che rappresenta un'istanza della System.Exception classe di base.
  • Oggetto eccezione che è un'istanza di un tipo di eccezione che eredita dalla classe di base. Ad esempio, un'istanza della InvalidCastException classe .

Esaminare le proprietà delle eccezioni

System.Exception è la classe base da cui ereditano tutti i tipi di eccezione derivati. Ogni tipo di eccezione eredita dalla classe base tramite una gerarchia di classi specifica. Ad esempio, la gerarchia di classi per è InvalidCastException la seguente:

Object
    Exception
        SystemException
            InvalidCastException

La maggior parte delle classi di eccezioni che ereditano da Exception non aggiungono funzionalità aggiuntive, ma ereditano semplicemente da Exception. Pertanto, esaminando le proprietà della Exception classe è possibile comprendere la maggior parte delle eccezioni e come usare un'eccezione nel codice.

Ecco le proprietà della Exception classe :

  • Dati: la Data proprietà contiene dati arbitrari nelle coppie chiave-valore.
  • HelpLink: la HelpLink proprietà può essere usata per contenere un URL (o URN) in un file della Guida che fornisce informazioni complete sulla causa di un'eccezione.
  • HResult: la HResult proprietà può essere usata per accedere a un valore numerico codificato assegnato a un'eccezione specifica.
  • InnerException: la InnerException proprietà può essere usata per creare e mantenere una serie di eccezioni durante la gestione delle eccezioni.
  • Messaggio: la Message proprietà fornisce informazioni dettagliate sulla causa di un'eccezione.
  • Origine: la Source proprietà può essere utilizzata per accedere al nome dell'applicazione o all'oggetto che causa l'errore.
  • StackTrace: la StackTrace proprietà contiene un'analisi dello stack che può essere usata per determinare dove si è verificato un errore.
  • TargetSite: la TargetSite proprietà può essere usata per ottenere il metodo che genera l'eccezione corrente.

Va bene se ci si sente un po' sopraffatti da questo esame delle proprietà delle eccezioni, delle classi di base e dell'ereditarietà. Non preoccuparti, intercettare le eccezioni nel codice e accedere alle proprietà di un'eccezione sono più semplici rispetto alla spiegazione del funzionamento delle eccezioni e delle proprietà delle eccezioni.

Annotazioni

In questo modulo verrà illustrato l'uso della proprietà del messaggio di un'eccezione per segnalare l'eccezione nell'interfaccia utente dell'applicazione.

Accedere alle proprietà di un oggetto eccezione

Ora che si conoscono gli oggetti eccezione e le relative proprietà, è possibile iniziare a scrivere codice.

  1. Aggiornare il file Program.cs come segue:

    try
    {
        Process1();
    }
    catch
    {
        Console.WriteLine("An exception has occurred");
    }
    
    Console.WriteLine("Exit program");
    
    static void Process1()
    {
        try
        {
            WriteMessage();
        }
        catch
        {
            Console.WriteLine("Exception caught in Process1");
        }
    }
    
    static void WriteMessage()
    {
        double float1 = 3000.0;
        double float2 = 0.0;
        int number1 = 3000;
        int number2 = 0;
    
        Console.WriteLine(float1 / float2);
        Console.WriteLine(number1 / number2);
    }
    
  2. Prenditi un minuto per esaminare il codice.

    Si tratta dello stesso codice visualizzato nell'unità precedente (il codice della soluzione per l'attività di verifica). Si sa che viene generata un'eccezione durante il WriteMessage metodo . Si sa anche che l'eccezione viene intercettata nel Process1 metodo . Questo codice verrà usato per esaminare gli oggetti eccezione e i tipi di eccezione specifici.

  3. Aggiornare il metodo Process1 come segue:

    static void Process1()
    {
        try
        {
            WriteMessage();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception caught in Process1: {ex.Message}");
        }
    }
    
  4. Esaminare gli aggiornamenti.

    Si noti che la clausola aggiornata catch rileva un'istanza della Exception classe in un oggetto denominato ex. Si noti anche che il Console.WriteLine() metodo usa ex per accedere alla proprietà dell'oggetto Message e visualizzare il messaggio di errore nella console.

    Anche se la catch clausola può essere usata senza argomenti, tale approccio non è consigliato. Se non si specifica un argomento, tutti i tipi di eccezione vengono intercettati e non è possibile decernare tra di essi.

    In generale, è consigliabile rilevare solo le eccezioni da cui il codice sa come eseguire il ripristino. Pertanto, la catch clausola deve specificare un argomento dell'oggetto derivato da System.Exception. Il tipo di eccezione deve essere il più specifico possibile. Ciò consente di evitare di intercettare le eccezioni che il gestore eccezioni non è in grado di risolvere. Il codice verrà aggiornato per intercettare un tipo di eccezione specifico più avanti in questo esercizio.

  5. Nel menu File, selezionare Salva.

  6. Impostare il punto di interruzione nella riga di codice seguente:

    Console.WriteLine($"Exception caught in Process1: {ex.Message}");
    
  7. Nel menu Esegui selezionare Avvia debug

    L'esecuzione del codice deve essere sospesa in corrispondenza del punto di interruzione.

  8. Passare il cursore del mouse su ex.

    Si noti che IntelliSense visualizza le stesse proprietà di eccezione esaminate in precedenza.

  9. Esaminare le informazioni che descrivono l'oggetto execcezione .

    Si noti che l'eccezione è un System.DivideByZeroException tipo di eccezione e che la Message proprietà è impostata su Attempted to divide by zero..

    Si noti che la StackTrace proprietà segnala il metodo e il numero di riga in cui si è verificato l'errore, insieme alla sequenza di chiamate al metodo (e ai numeri di riga) che hanno causato l'errore.

  10. Sulla barra degli strumenti Debug selezionare Continua.

  11. Esaminare l'output della console.

    Si noti che la proprietà dell'eccezione Message è inclusa nell'output generato dall'applicazione:

    ∞
    Exception caught in Process1: Attempted to divide by zero.
    Exit program
    

Intercettare un tipo di eccezione specifico

Ora che si conosce il tipo di eccezione da intercettare, è possibile aggiornare la catch clausola per gestire tale tipo di eccezione specifico.

  1. Aggiornare il metodo Process1 come segue:

    static void Process1()
    {
        try
        {
            WriteMessage();
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($"Exception caught in Process1: {ex.Message}");
        }
    }
    
  2. Salvare il codice e quindi avviare una sessione di debug.

  3. Si noti che l'applicazione aggiornata segnala gli stessi messaggi alla console.

    Anche se i messaggi segnalati sono gli stessi, esiste una differenza importante. Il Process1 metodo intercetta solo le eccezioni del tipo specifico che è pronto per gestire.

  4. Per generare un tipo di eccezione diverso, aggiornare il WriteMessage metodo nel modo seguente:

    static void WriteMessage()
    {
        double float1 = 3000.0;
        double float2 = 0.0;
        int number1 = 3000;
        int number2 = 0;
        byte smallNumber;
    
        Console.WriteLine(float1 / float2);
        // Console.WriteLine(number1 / number2);
        checked
        {
            smallNumber = (byte)number1;
        }
    }
    
  5. Si noti l'uso dell'istruzione checked .

    Quando si eseguono calcoli di tipo integrale che assegnano il valore di un tipo integrale a un altro tipo integrale, il risultato dipende dal contesto di controllo dell'overflow. In un checked contesto la conversione ha esito positivo se il valore di origine è compreso nell'intervallo del tipo di destinazione. In caso contrario, verrà generata un'eccezione OverflowException. In un contesto deselezionato, la conversione ha sempre esito positivo e procede come segue:

    • Se il tipo di origine è maggiore del tipo di destinazione, il valore di origine viene troncato rimuovendo i bit più significativi "extra". Il risultato viene quindi trattato come un valore del tipo di destinazione.

    • Se il tipo di origine è minore del tipo di destinazione, il valore di origine è esteso o esteso a zero in modo che corrisponda alla stessa dimensione del tipo di destinazione. L'estensione del segno viene usata se il tipo di origine è firmato; zero-extension viene usato se il tipo di origine non è firmato. Il risultato viene quindi trattato come un valore del tipo di destinazione.

    • Se il tipo di origine ha le stesse dimensioni del tipo di destinazione, il valore di origine viene considerato come valore del tipo di destinazione.

    Annotazioni

    I calcoli di tipo integrale che non si trovano all'interno di un checked blocco di codice vengono considerati come se fossero all'interno di un unchecked blocco di codice.

  6. Salvare il codice e quindi avviare una sessione di debug.

  7. Si noti che un nuovo tipo di eccezione viene intercettato dalla catch clausola nelle istruzioni di primo livello anziché all'interno del Process1 metodo .

    L'applicazione stampa i messaggi seguenti nella console:

    ∞
    An exception has occurred
    Exit program
    

    Annotazioni

    Il catch blocco in Process1 non viene eseguito. Questo è il comportamento desiderato. Rilevare solo le eccezioni che il codice è pronto per gestire.

Intercettare più eccezioni in un blocco di codice

A questo punto, è possibile chiedersi cosa accade quando si verificano più eccezioni in un singolo blocco di codice. Ogni eccezione verrà codificata catch man mano che si verificano?

  1. Aggiornare il metodo WriteMessage come segue:

    static void WriteMessage()
    {
        double float1 = 3000.0;
        double float2 = 0.0;
        int number1 = 3000;
        int number2 = 0;
        byte smallNumber;
    
        Console.WriteLine(float1 / float2);
        Console.WriteLine(number1 / number2);
        checked
        {
            smallNumber = (byte)number1;
        }
    }
    
  2. Impostare il punto di interruzione all'interno del WriteMessage() metodo nella riga di codice seguente:

    Console.WriteLine(float1 / float2);
    
  3. Salvare il codice e quindi avviare una sessione di debug.

  4. Eseguire il codice una riga alla volta e osservare cosa accade dopo che il codice gestisce la prima eccezione.

    Quando si verifica la prima eccezione, il controllo viene passato alla prima catch clausola che può gestire l'eccezione. Il codice che genera la seconda eccezione non viene mai raggiunto. Ciò significa che alcuni del codice non vengono mai eseguiti. Questo potrebbe causare gravi problemi.

  5. Prendere in considerazione il modo in cui è possibile gestire più eccezioni e quando/perché il codice potrebbe non gestire più eccezioni.

    Si è appreso in precedenza in questo modulo che le eccezioni devono essere rilevate il più vicino possibile alla posizione in cui si verificano. Tenendo presente questo aspetto, è possibile scegliere di aggiornare il WriteMessage metodo per intercettare le eccezioni usando il proprio try-catch. Per esempio:

    static void WriteMessage()
    {
        double float1 = 3000.0;
        double float2 = 0.0;
        int number1 = 3000;
        int number2 = 0;
        byte smallNumber;
    
        try
        {
            Console.WriteLine(float1 / float2);
            Console.WriteLine(number1 / number2);
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($"Exception caught in WriteMessage: {ex.Message}");
        }
        checked
        {
            smallNumber = (byte)number1;
        }
    }
    

    È anche possibile eseguire il wrapping del codice che causa l'oggetto OverflowException in un oggetto separato try-catch all'interno del WriteMessage() metodo .

    checked
    {
        try
        {
            smallNumber = (byte)number1;
        }
        catch (OverflowException ex)
        {
            Console.WriteLine($"Exception caught in WriteMessage: {ex.Message}");
        }  
    }
    
  6. In quali condizioni sarebbe indesiderabile rilevare le eccezioni successive?

    Si consideri il caso in cui il metodo (o il blocco di codice) stia completando un processo in due parti. Si supponga che la seconda parte del processo sia dipendente dal completamento della prima parte. Se la prima parte del processo non è in grado di completare correttamente, non è necessario continuare con la seconda parte del processo. In questo caso, è spesso preferibile presentare all'utente un messaggio che spiega la condizione di errore senza tentare la parte o le parti rimanenti del processo più grande.

Intercettare tipi di eccezione separati in un blocco di codice

In alcuni casi le variazioni nei dati possono causare diversi tipi di eccezioni.

  1. Cancellare i punti di interruzione e quindi sostituire il contenuto del file Program.cs con il codice seguente:

    // inputValues is used to store numeric values entered by a user
    string[] inputValues = new string[]{"three", "9999999999", "0", "2" };
    
    foreach (string inputValue in inputValues)
    {
        int numValue = 0;
        try
        {
            numValue = int.Parse(inputValue);
        }
        catch (FormatException)
        {
            Console.WriteLine("Invalid readResult. Please enter a valid number.");
        }
        catch (OverflowException)
        {
            Console.WriteLine("The number you entered is too large or too small.");
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
    
  2. Esaminare questo codice.

    Prima di tutto, il codice crea una matrice di stringhe denominata inputValues. I dati nella matrice devono rappresentare i valori di input immessi da un utente che ha richiesto di immettere valori numerici. A seconda del valore immesso, possono verificarsi tipi di eccezione diversi.

    Si noti che il codice usa il int.Parse metodo per convertire i valori stringa "input" in numeri interi. Il int.Parse codice viene inserito all'interno di un blocco di try codice.

  3. Impostare un punto di interruzione nella riga di codice seguente:

    int numValue = 0;
    
  4. Salvare il codice e quindi avviare una sessione di debug.

  5. Esaminare il codice una riga alla volta e notare che vengono rilevati diversi tipi di eccezione.

Riepilogo

Ecco i concetti più importanti di questa unità da ricordare:

  • La catch clausola deve essere configurata per intercettare un tipo di eccezione specifico. Ad esempio, il DivideByZeroException tipo di eccezione.
  • È possibile accedere alle proprietà di un oggetto eccezione all'interno del catch blocco. Ad esempio, è possibile usare la Message proprietà per informare l'utente dell'applicazione di un problema.
  • È possibile specificare due o più catch clausole quando è necessario intercettare più tipi di eccezione.