Cvičení – zachycení konkrétních typů výjimek

Dokončeno

Dříve v tomto modulu jste se dozvěděli, že objekty výjimek zachycené vaší aplikací jazyka C# jsou instancemi třídy výjimky. Obecně řečeno, váš kód bude catch mít jednu z následujících možností:

  • Objekt výjimky, který je instancí System.Exception základní třídy.
  • Objekt výjimky, který je instancí typu výjimky, který dědí ze základní třídy. Například instance InvalidCastException třídy.

Prozkoumání vlastností výjimky

System.Exception je základní třída, ze které dědí všechny odvozené typy výjimek. Každý typ výjimky dědí ze základní třídy prostřednictvím konkrétní hierarchie tříd. Například hierarchie tříd pro tuto InvalidCastException třídu je následující:

Object
    Exception
        SystemException
            InvalidCastException

Většina tříd výjimek, které dědí z Exception , nepřidají žádné další funkce; jednoduše dědí z Exception. Proto zkoumání vlastností Exception třídy umožňuje pochopit většinu výjimek a způsob použití výjimky v kódu.

Tady jsou vlastnosti Exception třídy:

  • Data: Vlastnost Data obsahuje libovolná data ve dvojicích klíč-hodnota.
  • HelpLink: Vlastnost HelpLink lze použít k uložení adresy URL (nebo URN) do souboru nápovědy, který poskytuje rozsáhlé informace o příčině výjimky.
  • HResult: Vlastnost HResult lze použít pro přístup k kódované číselné hodnotě, která je přiřazena ke konkrétní výjimce.
  • InnerException: Vlastnost InnerException lze použít k vytvoření a zachování řady výjimek během zpracování výjimek.
  • Zpráva: Vlastnost Message poskytuje podrobnosti o příčině výjimky.
  • Zdroj: Source Vlastnost lze použít pro přístup k názvu aplikace nebo objektu, který způsobuje chybu.
  • StackTrace: Vlastnost StackTrace obsahuje trasování zásobníku, které lze použít k určení, kde došlo k chybě.
  • TargetSite: Vlastnost TargetSite lze použít k získání metody, která vyvolá aktuální výjimku.

Je v pořádku, jestliže se cítíte trochu zahlceni tímto zkoumáním vlastností výjimek, základních tříd a dědičnosti. Nemějte obavy, zachycení výjimek v kódu a přístup k vlastnostem výjimky je jednodušší než vysvětlit, jak fungují výjimky a vlastnosti výjimky.

Poznámka:

V tomto modulu se zaměříte na použití vlastnosti zprávy výjimky k hlášení výjimky v uživatelském rozhraní vaší aplikace.

Přístup k vlastnostem objektu výjimky

Teď, když rozumíte objektům výjimek a jejich vlastnostem, je čas začít kódovat.

  1. Aktualizujte soubor Program.cs následujícím způsobem:

    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. Projděte si kód za minutu.

    Jedná se o stejný kód, který jste viděli v předchozí lekci (kód řešení pro aktivitu výzvy). Víte, že během WriteMessage metody je vyvolána výjimka. Také víte, že výjimka je zachycena v metodě Process1. Tento kód použijete k prozkoumání objektů výjimek a konkrétních typů výjimek.

  3. Aktualizujte metodu Process1 následujícím způsobem:

    static void Process1()
    {
        try
        {
            WriteMessage();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception caught in Process1: {ex.Message}");
        }
    }
    
  4. Prohlédněte si aktualizace za chvilku.

    Všimněte si, že aktualizovaná catch klauzule zachytává instanci Exception třídy v objektu s názvem ex. Všimněte si také, že vaše Console.WriteLine() metoda používá ex k přístupu k vlastnosti objektu Message a zobrazuje chybovou zprávu na konzoli.

    catch I když se klauzule dá použít bez argumentů, tento přístup se nedoporučuje. Pokud argument nezadáte, všechny typy výjimek se zachytí a nelze mezi nimi rozlišovat.

    Obecně platí, že byste měli zachytit pouze výjimky, ze kterých se váš kód umí zotavit. catch Klauzule by proto měla určovat argument objektu, který je odvozen z System.Exception. Typ výjimky by měl být co nejvíce specifický. To pomáhá vyhnout se zachycení výjimek, které obslužná rutina výjimek nedokáže vyřešit. Kód aktualizujete tak, aby zachytil konkrétní typ výjimky později v tomto cvičení.

  5. V nabídce Soubor vyberte Uložit.

  6. Nastavte zarážku na následujícím řádku kódu:

    Console.WriteLine($"Exception caught in Process1: {ex.Message}");
    
  7. V nabídce Spustit vyberte Spustit ladění.

    Provádění kódu by se mělo pozastavit na bodu přerušení.

  8. Najeďte myší na ex.

    Všimněte si, že IntelliSense zobrazuje stejné vlastnosti výjimek, které jste prozkoumali dříve.

  9. Chvíli se podívejte na informace popisující objekt exvýjimky .

    Všimněte si, že výjimka je System.DivideByZeroException typ výjimky a že Message vlastnost je nastavena na Attempted to divide by zero..

    Všimněte si, že StackTrace vlastnost hlásí metodu a číslo řádku, kde došlo k chybě, spolu s posloupností volání metody (a čísel řádků), která vedla k chybě.

  10. Na panelu nástrojů Ladění vyberte Pokračovat.

  11. Prohlédněte si výstup konzoly.

    Všimněte si, že vlastnost výjimky Message je součástí výstupu vygenerovaného vaší aplikací:

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

Zachycení konkrétního typu výjimky

Teď, když znáte typ výjimky, kterou chcete zachytit, můžete aktualizovat catch klauzuli tak, aby zpracovávala tento konkrétní typ výjimky.

  1. Aktualizujte metodu Process1 následujícím způsobem:

    static void Process1()
    {
        try
        {
            WriteMessage();
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($"Exception caught in Process1: {ex.Message}");
        }
    }
    
  2. Uložte kód a spusťte ladicí relaci.

  3. Všimněte si, že vaše aktualizovaná aplikace hlásí stejné zprávy do konzoly.

    I když jsou nahlášené zprávy stejné, existuje důležitý rozdíl. Vaše Process1 metoda zachytí pouze výjimky konkrétního typu, který je připravený zpracovat.

  4. Pokud chcete vygenerovat jiný typ výjimky, aktualizujte metodu WriteMessage následujícím způsobem:

    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. Všimněte si použití checked příkazu.

    Při provádění výpočtů integrálního typu, které přiřazují hodnotu jednoho celočíselného typu jinému celočíselnému typu, závisí výsledek na kontextu kontroly přetečení. checked V kontextu bude převod úspěšný, pokud zdrojová hodnota spadá do rozsahu cílového typu. V opačném případě se vyvolá OverflowException. V nezaškrtnutém kontextu bude převod vždy úspěšný a bude pokračovat následujícím způsobem:

    • Pokud je zdrojový typ větší než cílový typ, je zdrojová hodnota zkrácena zrušením jeho "extra" nejvýznamnějších bitů. Výsledek se pak považuje za hodnotu cílového typu.

    • Pokud je typ zdroje menší než cílový typ, je zdrojová hodnota buď prodloužena, nebo je rozšířena nulou, aby byla stejná jako cílový typ. Rozšíření podpisu se používá, pokud je typ zdroje podepsán; Pokud je typ zdroje nepodepsaný, použije se nulové rozšíření. Výsledek se pak považuje za hodnotu cílového typu.

    • Pokud je typ zdroje stejná jako cílový typ, je zdrojová hodnota považována za hodnotu cílového typu.

    Poznámka:

    Výpočty celočíselného checked typu, které nejsou uvnitř bloku kódu, jsou považovány za ty, které jsou uvnitř unchecked bloku kódu.

  6. Uložte váš kód a spusťte ladicí relaci.

  7. Všimněte si, že nový typ výjimky je zachycen catch klauzulí v příkazech nejvyšší úrovně, nikoli uvnitř Process1 metody.

    Aplikace vypíše do konzoly následující zprávy:

    ∞
    An exception has occurred
    Exit program
    

    Poznámka:

    Blok catch v Process1 se neprovede. Toto je chování, které jste chtěli. Zachyťte pouze výjimky, které je váš kód připravený zpracovat.

Zachycení více výjimek v bloku kódu

V tuto chvíli vás může zajímat, co se stane, když v jednom bloku kódu dojde k několika výjimkám. Bude váš kód zachytávat každou výjimku catch při jejím výskytu?

  1. Aktualizujte metodu WriteMessage následujícím způsobem:

    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. Nastavte zarážku uvnitř WriteMessage() metody na následujícím řádku kódu:

    Console.WriteLine(float1 / float2);
    
  3. Uložte kód a spusťte ladicí relaci.

  4. Procházejte svůj kód jednu řádku po druhé a všimněte si, co se stane poté, co váš kód zpracuje první výjimku.

    Když dojde k první výjimce, je ovládací prvek předán první catch klauzuli, která může zpracovat výjimku. Kód, který by vygeneroval druhou výjimku, není nikdy dosažen. To znamená, že se některý z vašich kódů nikdy nespustí. To by mohlo vést k vážným problémům.

  5. Chvíli zvažte, jak můžete spravovat více výjimek a kdy/proč nechcete, aby kód spravil více výjimek.

    Dozvěděli jste se dříve v tomto modulu, že výjimky by měly být zachyceny co nejblíže tam, kde se vyskytují. S ohledem na to se můžete rozhodnout aktualizovat metodu WriteMessage, aby zachytila výjimky pomocí vlastní try-catch. Například:

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

    Můžete také zabalit kód, který způsobuje OverflowException, do samostatného try-catch v rámci metody WriteMessage().

    checked
    {
        try
        {
            smallNumber = (byte)number1;
        }
        catch (OverflowException ex)
        {
            Console.WriteLine($"Exception caught in WriteMessage: {ex.Message}");
        }  
    }
    
  6. Za jakých podmínek by bylo nežádoucí zachytit další výjimky?

    Vezměte v úvahu případ, kdy vaše metoda (nebo blok kódu) provádí dva části procesu. Předpokládejme, že druhá část procesu závisí na dokončení první části. Pokud se první část procesu nepodaří úspěšně dokončit, není nutné pokračovat na druhou část procesu. V tomto případě je často lepší prezentovat uživatele zprávou s vysvětlením chybového stavu, aniž byste se pokusili o zbývající část nebo části většího procesu.

Zachycení samostatných typů výjimek v bloku kódu

Existují časy, kdy varianty dat můžou způsobit různé typy výjimek.

  1. Vymažte zarážky a nahraďte obsah souboru Program.cs následujícím kódem:

    // 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. Přečtěte si tento kód za chvilku.

    Nejprve kód vytvoří řetězcové pole pojmenované inputValues. Data v poli představují vstupní hodnoty zadané uživatelem, který byl vyzván k zadání číselných hodnot. V závislosti na zadané hodnotě může dojít k různým typům výjimek.

    Všimněte si, že kód používá metodu int.Parse k převodu řetězcových "vstupních" hodnot na celá čísla. Kód int.Parse se umístí do try bloku kódu.

  3. Nastavte zarážku na následujícím řádku kódu:

    int numValue = 0;
    
  4. Uložte kód a spusťte ladicí relaci.

  5. Krokujte kódem po jednom řádku a všimněte si, že jsou zachyceny různé typy výjimek.

Rekapitulace

Tady je několik důležitých věcí, které je potřeba si z této lekce zapamatovat:

  • Klauzule catch by měla být nakonfigurovaná tak, aby zachytila určitý typ výjimky. Například DivideByZeroException typ výjimky.
  • K vlastnostem objektu výjimky lze přistupovat v rámci catch bloku. Vlastnost můžete například použít Message k informování uživatele aplikace o problému.
  • Pokud potřebujete zachytit více než jeden typ výjimky, můžete zadat dvě nebo více catch klauzulí.