System.Exception, klasa

Ten artykuł zawiera dodatkowe uwagi dotyczące dokumentacji referencyjnej dla tego interfejsu API.

Klasa Exception jest klasą bazową dla wszystkich wyjątków. W przypadku wystąpienia błędu system lub aktualnie wykonująca aplikację zgłasza go, zgłaszając wyjątek zawierający informacje o błędzie. Po wystąpieniu wyjątku jest on obsługiwany przez aplikację lub przez domyślną procedurę obsługi wyjątków.

Błędy i wyjątki

Błędy czasu wykonywania mogą wystąpić z różnych powodów. Jednak nie wszystkie błędy powinny być obsługiwane jako wyjątki w kodzie. Poniżej przedstawiono niektóre kategorie błędów, które mogą wystąpić w czasie wykonywania i odpowiednie sposoby reagowania na nie.

  • Błędy użycia. Błąd użycia reprezentuje błąd logiki programu, który może spowodować wyjątek. Jednak błąd powinien zostać rozwiązany nie za pomocą obsługi wyjątków, ale przez zmodyfikowanie wadliwego kodu. Na przykład zastąpienie Object.Equals(Object) metody w poniższym przykładzie zakłada, że obj argument musi być zawsze inny niż null.

    using System;
    
    public class Person1
    {
       private string _name;
    
       public string Name
       {
          get { return _name; }
          set { _name = value; }
       }
    
       public override int GetHashCode()
       {
          return this.Name.GetHashCode();
       }
    
       public override bool Equals(object obj)
       {
          // This implementation contains an error in program logic:
          // It assumes that the obj argument is not null.
          Person1 p = (Person1) obj;
          return this.Name.Equals(p.Name);
       }
    }
    
    public class UsageErrorsEx1
    {
       public static void Main()
       {
          Person1 p1 = new Person1();
          p1.Name = "John";
          Person1 p2 = null;
    
          // The following throws a NullReferenceException.
          Console.WriteLine("p1 = p2: {0}", p1.Equals(p2));
       }
    }
    
    // In F#, null is not a valid state for declared types 
    // without 'AllowNullLiteralAttribute'
    [<AllowNullLiteral>]
    type Person() =
        member val Name = "" with get, set
    
        override this.GetHashCode() =
            this.Name.GetHashCode()
    
        override this.Equals(obj) =
            // This implementation contains an error in program logic:
            // It assumes that the obj argument is not null.
            let p = obj :?> Person
            this.Name.Equals p.Name
    
    let p1 = Person()
    p1.Name <- "John"
    let p2: Person = null
    
    // The following throws a NullReferenceException.
    printfn $"p1 = p2: {p1.Equals p2}"
    
    Public Class Person
       Private _name As String
       
       Public Property Name As String
          Get
             Return _name
          End Get
          Set
             _name = value
          End Set
       End Property
       
       Public Overrides Function Equals(obj As Object) As Boolean
          ' This implementation contains an error in program logic:
          ' It assumes that the obj argument is not null.
          Dim p As Person = CType(obj, Person)
          Return Me.Name.Equals(p.Name)
       End Function
    End Class
    
    Module Example2
        Public Sub Main()
            Dim p1 As New Person()
            p1.Name = "John"
            Dim p2 As Person = Nothing
    
            ' The following throws a NullReferenceException.
            Console.WriteLine("p1 = p2: {0}", p1.Equals(p2))
        End Sub
    End Module
    

    Wyjątek NullReferenceException , który powoduje, że wyniki, gdy objnull można wyeliminować, modyfikując kod źródłowy w celu jawnego przetestowania wartości null przed wywołaniem Object.Equals zastąpienia, a następnie ponownie skompilując. Poniższy przykład zawiera poprawiony kod źródłowy, który obsługuje null argument.

    using System;
    
    public class Person2
    {
        private string _name;
    
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
    
        public override int GetHashCode()
        {
            return this.Name.GetHashCode();
        }
    
        public override bool Equals(object obj)
        {
            // This implementation handles a null obj argument.
            Person2 p = obj as Person2;
            if (p == null)
                return false;
            else
                return this.Name.Equals(p.Name);
        }
    }
    
    public class UsageErrorsEx2
    {
        public static void Main()
        {
            Person2 p1 = new Person2();
            p1.Name = "John";
            Person2 p2 = null;
    
            Console.WriteLine("p1 = p2: {0}", p1.Equals(p2));
        }
    }
    // The example displays the following output:
    //        p1 = p2: False
    
    // In F#, null is not a valid state for declared types 
    // without 'AllowNullLiteralAttribute'
    [<AllowNullLiteral>]
    type Person() =
        member val Name = "" with get, set
    
        override this.GetHashCode() =
            this.Name.GetHashCode()
    
        override this.Equals(obj) =
            // This implementation handles a null obj argument.
            match obj with
            | :? Person as p -> 
                this.Name.Equals p.Name
            | _ ->
                false
    
    let p1 = Person()
    p1.Name <- "John"
    let p2: Person = null
    
    printfn $"p1 = p2: {p1.Equals p2}"
    // The example displays the following output:
    //        p1 = p2: False
    
    Public Class Person2
        Private _name As String
    
        Public Property Name As String
            Get
                Return _name
            End Get
            Set
                _name = Value
            End Set
        End Property
    
        Public Overrides Function Equals(obj As Object) As Boolean
            ' This implementation handles a null obj argument.
            Dim p As Person2 = TryCast(obj, Person2)
            If p Is Nothing Then
                Return False
            Else
                Return Me.Name.Equals(p.Name)
            End If
        End Function
    End Class
    
    Module Example3
        Public Sub Main()
            Dim p1 As New Person2()
            p1.Name = "John"
            Dim p2 As Person2 = Nothing
    
            Console.WriteLine("p1 = p2: {0}", p1.Equals(p2))
        End Sub
    End Module
    ' The example displays the following output:
    '       p1 = p2: False
    

    Zamiast używać obsługi wyjątków dla błędów użycia, można użyć Debug.Assert metody do identyfikowania błędów użycia w kompilacjach debugowania oraz Trace.Assert metody identyfikowania błędów użycia zarówno w kompilacjach debugowania, jak i kompilacji wydania. Aby uzyskać więcej informacji, zobacz Asercji w kodzie zarządzanym.

  • Błędy programu. Błąd programu jest błędem czasu wykonywania, którego nie można uniknąć przez napisanie kodu bez usterek.

    W niektórych przypadkach błąd programu może odzwierciedlać oczekiwany lub rutynowy stan błędu. W takim przypadku możesz unikać używania obsługi wyjątków w celu obsługi błędów programu i zamiast tego ponowić próbę wykonania operacji. Jeśli na przykład użytkownik ma wprowadzić datę w określonym formacie, możesz przeanalizować ciąg daty, wywołując DateTime.TryParseExact metodę , która zwraca wartość wskazującą Boolean , czy operacja analizowania zakończyła się pomyślnie, zamiast używać DateTime.ParseExact metody , która zgłasza FormatException wyjątek, jeśli nie można przekonwertować ciągu daty na DateTime wartość. Podobnie, jeśli użytkownik próbuje otworzyć plik, który nie istnieje, możesz najpierw wywołać File.Exists metodę , aby sprawdzić, czy plik istnieje, a jeśli nie, monituj użytkownika, czy chce go utworzyć.

    W innych przypadkach błąd programu odzwierciedla nieoczekiwany warunek błędu, który można obsłużyć w kodzie. Na przykład, nawet jeśli sprawdzono, czy plik istnieje, może zostać usunięty, zanim będzie można go otworzyć lub może być uszkodzony. W takim przypadku próba otwarcia pliku przez utworzenie wystąpienia obiektu lub wywołanie StreamReaderOpen metody może zgłosić FileNotFoundException wyjątek. W takich przypadkach należy użyć obsługi wyjątków, aby odzyskać sprawę po błędzie.

  • Błędy systemowe. Awaria systemu to błąd czasu wykonywania, który nie może być obsługiwany programowo w zrozumiały sposób. Na przykład każda metoda może zgłosić OutOfMemoryException wyjątek, jeśli środowisko uruchomieniowe języka wspólnego nie może przydzielić dodatkowej pamięci. Zazwyczaj awarie systemu nie są obsługiwane przy użyciu obsługi wyjątków. Zamiast tego może być możliwe użycie zdarzenia, takiego jak AppDomain.UnhandledException i wywołanie Environment.FailFast metody w celu rejestrowania informacji o wyjątkach i powiadamiania użytkownika o niepowodzeniu przed zakończeniem działania aplikacji.

Bloki try/catch

Środowisko uruchomieniowe języka wspólnego udostępnia model obsługi wyjątków oparty na reprezentacji wyjątków jako obiektów oraz rozdzielenie kodu programu i obsługi wyjątków w try blokach i catch blokach. Może istnieć jeden lub więcej catch bloków, z których każda została zaprojektowana do obsługi określonego typu wyjątku, lub jeden blok zaprojektowany w celu przechwycenia bardziej szczegółowego wyjątku niż inny blok.

Jeśli aplikacja obsługuje wyjątki występujące podczas wykonywania bloku kodu aplikacji, kod musi zostać umieszczony w try instrukcji i jest nazywany blokiem try . Kod aplikacji, który obsługuje wyjątki zgłaszane przez try blok, jest umieszczany w catch instrukcji i jest nazywany blokiem catch . Z blokiem try jest skojarzonych zero lub więcej catch bloków, a każdy catch blok zawiera filtr typu, który określa typy obsługiwanych wyjątków.

W przypadku wystąpienia wyjątku w try bloku system przeszukuje skojarzone catch bloki w kolejności, w której są wyświetlane w kodzie aplikacji, dopóki nie znajdzie bloku obsługującego catch wyjątek. Blok catch obsługuje wyjątek typu T , jeśli filtr typu bloku catch określa T lub dowolny typ, z którego T pochodzi. System zatrzymuje wyszukiwanie po znalezieniu pierwszego catch bloku obsługującego wyjątek. Z tego powodu w kodzie aplikacji należy określić blok obsługujący catch typ przed blokiem catch obsługującym jego typy podstawowe, jak pokazano w przykładzie, który jest zgodny z tą sekcją. Ostatnio określono blok catch, który obsługuje System.Exception .

Jeśli żaden z bloków catch skojarzonych z bieżącym try blokiem nie obsługuje wyjątku, a bieżący try blok jest zagnieżdżony w innych try blokach w bieżącym wywołaniu, catch bloki skojarzone z następnym otaczającym try blokiem są przeszukiwane. Jeśli nie catch zostanie znaleziony żaden blok dla wyjątku, system wyszukuje poprzednie poziomy zagnieżdżenia w bieżącym wywołaniu. Jeśli w bieżącym wywołaniu nie catch zostanie znaleziony żaden blok wyjątku, wyjątek zostanie przekazany do stosu wywołań, a poprzednia ramka stosu zostanie przeszukana pod kątem bloku obsługującego catch wyjątek. Wyszukiwanie stosu wywołań będzie kontynuowane do momentu obsługi wyjątku lub do momentu, aż w stosie wywołań nie istnieją żadne ramki. Jeśli górna część stosu wywołań zostanie osiągnięta bez znalezienia bloku obsługującego catch wyjątek, domyślna procedura obsługi wyjątków obsługuje go i aplikacja kończy działanie.

Wypróbuj język F#. z wyrażeniem

Język F# nie używa catch bloków. Zamiast tego zgłoszony wyjątek jest dopasowywany do wzorca przy użyciu pojedynczego with bloku. Ponieważ jest to wyrażenie, a nie instrukcja, wszystkie ścieżki muszą zwracać ten sam typ. Aby dowiedzieć się więcej, zobacz Try... z wyrażeniem.

Funkcje typu wyjątku

Typy wyjątków obsługują następujące funkcje:

  • Czytelny dla człowieka tekst opisujący błąd. W przypadku wystąpienia wyjątku środowisko uruchomieniowe udostępnia komunikat tekstowy informujący użytkownika o charakterze błędu i sugerowanie akcji w celu rozwiązania problemu. Ta wiadomość tekstowa jest przechowywana we Message właściwości obiektu wyjątku. Podczas tworzenia obiektu wyjątku można przekazać ciąg tekstowy do konstruktora, aby opisać szczegóły tego konkretnego wyjątku. Jeśli do konstruktora nie podano argumentu komunikatu o błędzie, zostanie użyty domyślny komunikat o błędzie. Aby uzyskać więcej informacji, zobacz Message właściwość .

  • Stan stosu wywołań, gdy wyjątek został zgłoszony. Właściwość StackTrace zawiera ślad stosu, którego można użyć do określenia, gdzie występuje błąd w kodzie. Ślad stosu zawiera listę wszystkich wywoływanych metod i numerów wierszy w pliku źródłowym, w którym są wykonywane wywołania.

Właściwości klasy wyjątku

Klasa Exception zawiera wiele właściwości, które pomagają zidentyfikować lokalizację kodu, typ, plik pomocy i przyczynę wyjątku: StackTrace, HResultInnerExceptionHelpLinkSourceMessageTargetSitei .Data

Gdy relacja przyczynowa istnieje między co najmniej dwoma wyjątkami, InnerException właściwość przechowuje te informacje. Wyjątek zewnętrzny jest zgłaszany w odpowiedzi na ten wyjątek wewnętrzny. Kod, który obsługuje wyjątek zewnętrzny, może używać informacji z wcześniejszego wyjątku wewnętrznego w celu bardziej odpowiedniego obsługi błędu. Dodatkowe informacje o wyjątku mogą być przechowywane jako kolekcja par klucz/wartość we Data właściwości .

Ciąg komunikatu o błędzie przekazywany do konstruktora podczas tworzenia obiektu wyjątku powinien być zlokalizowany i można go dostarczyć z pliku zasobu przy użyciu ResourceManager klasy . Aby uzyskać więcej informacji na temat zlokalizowanych zasobów, zobacz Tworzenie zestawów satelickich i tworzenie pakietów i wdrażanie zasobów .

Aby udostępnić użytkownikowi obszerne informacje o przyczynie wystąpienia wyjątku, HelpLink właściwość może przechowywać adres URL (lub URN) w pliku pomocy.

Klasa Exception używa wartości HRESULT COR_E_EXCEPTION, która ma wartość 0x80131500.

Aby uzyskać listę początkowych wartości właściwości dla wystąpienia Exception klasy, zobacz Exception konstruktory.

Zagadnienia dotyczące wydajności

Zgłaszanie lub obsługa wyjątku zużywa znaczną ilość zasobów systemowych i czasu wykonywania. Zgłaszaj wyjątki tylko do obsługi naprawdę nadzwyczajnych warunków, a nie do obsługi przewidywalnych zdarzeń ani sterowania przepływem. Na przykład w niektórych przypadkach, takich jak podczas tworzenia biblioteki klas, uzasadnione jest zgłoszenie wyjątku, jeśli argument metody jest nieprawidłowy, ponieważ oczekujesz, że metoda zostanie wywołana z prawidłowymi parametrami. Nieprawidłowy argument metody, jeśli nie jest wynikiem błędu użycia, oznacza, że wystąpił coś niezwykłego. Z drugiej strony nie zgłaszaj wyjątku, jeśli dane wejściowe użytkownika są nieprawidłowe, ponieważ użytkownicy mogą od czasu do czasu wprowadzać nieprawidłowe dane. Zamiast tego podaj mechanizm ponawiania prób, aby użytkownicy mogli wprowadzać prawidłowe dane wejściowe. Nie należy też używać wyjątków do obsługi błędów użycia. Zamiast tego użyj asercji , aby zidentyfikować i poprawić błędy użycia.

Ponadto nie zgłaszaj wyjątku, gdy kod powrotny jest wystarczający; nie konwertuj kodu powrotnego na wyjątek; i nie należy rutynowo przechwytywać wyjątku, ignorować go, a następnie kontynuować przetwarzanie.

Ponowne zgłaszanie wyjątku

W wielu przypadkach program obsługi wyjątków po prostu chce przekazać wyjątek do elementu wywołującego. Najczęściej występuje to w:

  • Biblioteka klas, która z kolei opakowuje wywołania metod w bibliotece klas platformy .NET lub innych bibliotekach klas.

  • Aplikacja lub biblioteka, która napotka wyjątek krytyczny. Procedura obsługi wyjątków może zarejestrować wyjątek, a następnie ponownie zgłosić wyjątek.

Zalecanym sposobem ponownego zgłaszania wyjątku jest po prostu użycie instrukcji throw w języku C#, funkcji reraise w języku F# i instrukcji Throw w Visual Basic bez uwzględniania wyrażenia. Dzięki temu wszystkie informacje o stosie wywołań są zachowywane po propagacji wyjątku do elementu wywołującego. Ilustruje to poniższy przykład. Metoda rozszerzenia ciągu, FindOccurrences, opakowuje jedno lub więcej wywołań bez String.IndexOf(String, Int32) sprawdzania poprawności argumentów wcześniej.

using System;
using System.Collections.Generic;

public static class Library1
{
    public static int[] FindOccurrences(this String s, String f)
    {
        var indexes = new List<int>();
        int currentIndex = 0;
        try
        {
            while (currentIndex >= 0 && currentIndex < s.Length)
            {
                currentIndex = s.IndexOf(f, currentIndex);
                if (currentIndex >= 0)
                {
                    indexes.Add(currentIndex);
                    currentIndex++;
                }
            }
        }
        catch (ArgumentNullException)
        {
            // Perform some action here, such as logging this exception.

            throw;
        }
        return indexes.ToArray();
    }
}
open System

module Library = 
    let findOccurrences (s: string) (f: string) =
        let indexes = ResizeArray()
        let mutable currentIndex = 0
        try
            while currentIndex >= 0 && currentIndex < s.Length do
                currentIndex <- s.IndexOf(f, currentIndex)
                if currentIndex >= 0 then
                    indexes.Add currentIndex
                    currentIndex <- currentIndex + 1
        with :? ArgumentNullException ->
            // Perform some action here, such as logging this exception.
            reraise ()
        indexes.ToArray()
Imports System.Collections.Generic
Imports System.Runtime.CompilerServices

Public Module Library
    <Extension()>
    Public Function FindOccurrences1(s As String, f As String) As Integer()
        Dim indexes As New List(Of Integer)
        Dim currentIndex As Integer = 0
        Try
            Do While currentIndex >= 0 And currentIndex < s.Length
                currentIndex = s.IndexOf(f, currentIndex)
                If currentIndex >= 0 Then
                    indexes.Add(currentIndex)
                    currentIndex += 1
                End If
            Loop
        Catch e As ArgumentNullException
            ' Perform some action here, such as logging this exception.

            Throw
        End Try
        Return indexes.ToArray()
    End Function
End Module

Następnie wywołujący wywołuje FindOccurrences dwa razy. W drugim wywołaniu metody FindOccurrencesobiekt wywołujący przekazuje null element jako ciąg wyszukiwania, co powoduje String.IndexOf(String, Int32) , że metoda zgłasza ArgumentNullException wyjątek. Ten wyjątek jest obsługiwany przez metodę FindOccurrences i przekazywany z powrotem do elementu wywołującego. Ponieważ instrukcja throw jest używana bez wyrażenia, dane wyjściowe z przykładu pokazują, że stos wywołań jest zachowywany.

public class RethrowEx1
{
    public static void Main()
    {
        String s = "It was a cold day when...";
        int[] indexes = s.FindOccurrences("a");
        ShowOccurrences(s, "a", indexes);
        Console.WriteLine();

        String toFind = null;
        try
        {
            indexes = s.FindOccurrences(toFind);
            ShowOccurrences(s, toFind, indexes);
        }
        catch (ArgumentNullException e)
        {
            Console.WriteLine("An exception ({0}) occurred.",
                              e.GetType().Name);
            Console.WriteLine("Message:\n   {0}\n", e.Message);
            Console.WriteLine("Stack Trace:\n   {0}\n", e.StackTrace);
        }
    }

    private static void ShowOccurrences(String s, String toFind, int[] indexes)
    {
        Console.Write("'{0}' occurs at the following character positions: ",
                      toFind);
        for (int ctr = 0; ctr < indexes.Length; ctr++)
            Console.Write("{0}{1}", indexes[ctr],
                          ctr == indexes.Length - 1 ? "" : ", ");

        Console.WriteLine();
    }
}
// The example displays the following output:
//    'a' occurs at the following character positions: 4, 7, 15
//
//    An exception (ArgumentNullException) occurred.
//    Message:
//       Value cannot be null.
//    Parameter name: value
//
//    Stack Trace:
//          at System.String.IndexOf(String value, Int32 startIndex, Int32 count, Stri
//    ngComparison comparisonType)
//       at Library.FindOccurrences(String s, String f)
//       at Example.Main()
open Library

let showOccurrences toFind (indexes: int[]) =
    printf $"'{toFind}' occurs at the following character positions: "
    for i = 0 to indexes.Length - 1 do
        printf $"""{indexes[i]}{if i = indexes.Length - 1 then "" else ", "}"""
    printfn ""

let s = "It was a cold day when..."
let indexes = findOccurrences s "a"
showOccurrences "a" indexes
printfn ""

let toFind: string = null
try
    let indexes = findOccurrences s toFind
    showOccurrences toFind indexes

with :? ArgumentNullException as e ->
    printfn $"An exception ({e.GetType().Name}) occurred."
    printfn $"Message:\n   {e.Message}\n"
    printfn $"Stack Trace:\n   {e.StackTrace}\n"

// The example displays the following output:
//    'a' occurs at the following character positions: 4, 7, 15
//
//    An exception (ArgumentNullException) occurred.
//    Message:
//       Value cannot be null. (Parameter 'value')
//
//    Stack Trace:
//          at System.String.IndexOf(String value, Int32 startIndex, Int32 count, Stri
//    ngComparison comparisonType)
//       at Library.findOccurrences(String s, String f)
//       at <StartupCode$fs>.main@()
Module Example1
    Public Sub Main()
        Dim s As String = "It was a cold day when..."
        Dim indexes() As Integer = s.FindOccurrences1("a")
        ShowOccurrences(s, "a", indexes)
        Console.WriteLine()

        Dim toFind As String = Nothing
        Try
            indexes = s.FindOccurrences1(toFind)
            ShowOccurrences(s, toFind, indexes)
        Catch e As ArgumentNullException
            Console.WriteLine("An exception ({0}) occurred.",
                           e.GetType().Name)
            Console.WriteLine("Message:{0}   {1}{0}", vbCrLf, e.Message)
            Console.WriteLine("Stack Trace:{0}   {1}{0}", vbCrLf, e.StackTrace)
        End Try
    End Sub

    Private Sub ShowOccurrences(s As String, toFind As String, indexes As Integer())
        Console.Write("'{0}' occurs at the following character positions: ",
                    toFind)
        For ctr As Integer = 0 To indexes.Length - 1
            Console.Write("{0}{1}", indexes(ctr),
                       If(ctr = indexes.Length - 1, "", ", "))
        Next
        Console.WriteLine()
    End Sub
End Module
' The example displays the following output:
'    'a' occurs at the following character positions: 4, 7, 15
'
'    An exception (ArgumentNullException) occurred.
'    Message:
'       Value cannot be null.
'    Parameter name: value
'
'    Stack Trace:
'          at System.String.IndexOf(String value, Int32 startIndex, Int32 count, Stri
'    ngComparison comparisonType)
'       at Library.FindOccurrences(String s, String f)
'       at Example.Main()

W przeciwieństwie do tego, jeśli wyjątek jest zgłaszany ponownie przy użyciu tej instrukcji:

throw e;
Throw e
raise e

... następnie pełny stos wywołań nie jest zachowywany, a przykład generuje następujące dane wyjściowe:

'a' occurs at the following character positions: 4, 7, 15

An exception (ArgumentNullException) occurred.
Message:
   Value cannot be null.
Parameter name: value

Stack Trace:
      at Library.FindOccurrences(String s, String f)
   at Example.Main()

Nieco bardziej uciążliwą alternatywą jest zgłoszenie nowego wyjątku i zachowanie informacji o stosie wywołań oryginalnego wyjątku w wyjątku wewnętrznym. Obiekt wywołujący może następnie użyć właściwości nowego wyjątku InnerException , aby pobrać ramkę stosu i inne informacje o oryginalnym wyjątku. W takim przypadku instrukcja throw to:

throw new ArgumentNullException("You must supply a search string.", e);
raise (ArgumentNullException("You must supply a search string.", e) )
Throw New ArgumentNullException("You must supply a search string.",
                             e)

Kod użytkownika, który obsługuje wyjątek, musi wiedzieć, że InnerException właściwość zawiera informacje o oryginalnym wyjątku, jak pokazano w poniższym procedurze obsługi wyjątków.

try
{
    indexes = s.FindOccurrences(toFind);
    ShowOccurrences(s, toFind, indexes);
}
catch (ArgumentNullException e)
{
    Console.WriteLine("An exception ({0}) occurred.",
                      e.GetType().Name);
    Console.WriteLine("   Message:\n{0}", e.Message);
    Console.WriteLine("   Stack Trace:\n   {0}", e.StackTrace);
    Exception ie = e.InnerException;
    if (ie != null)
    {
        Console.WriteLine("   The Inner Exception:");
        Console.WriteLine("      Exception Name: {0}", ie.GetType().Name);
        Console.WriteLine("      Message: {0}\n", ie.Message);
        Console.WriteLine("      Stack Trace:\n   {0}\n", ie.StackTrace);
    }
}
// The example displays the following output:
//    'a' occurs at the following character positions: 4, 7, 15
//
//    An exception (ArgumentNullException) occurred.
//       Message: You must supply a search string.
//
//       Stack Trace:
//          at Library.FindOccurrences(String s, String f)
//       at Example.Main()
//
//       The Inner Exception:
//          Exception Name: ArgumentNullException
//          Message: Value cannot be null.
//    Parameter name: value
//
//          Stack Trace:
//          at System.String.IndexOf(String value, Int32 startIndex, Int32 count, Stri
//    ngComparison comparisonType)
//       at Library.FindOccurrences(String s, String f)
try
    let indexes = findOccurrences s toFind
    showOccurrences toFind indexes
with :? ArgumentNullException as e ->
    printfn $"An exception ({e.GetType().Name}) occurred."
    printfn $"   Message:\n{e.Message}"
    printfn $"   Stack Trace:\n   {e.StackTrace}"
    let ie = e.InnerException
    if ie <> null then
        printfn "   The Inner Exception:"
        printfn $"      Exception Name: {ie.GetType().Name}"
        printfn $"      Message: {ie.Message}\n"
        printfn $"      Stack Trace:\n   {ie.StackTrace}\n"
// The example displays the following output:
//    'a' occurs at the following character positions: 4, 7, 15
//
//    An exception (ArgumentNullException) occurred.
//       Message: You must supply a search string.
//
//       Stack Trace:
//          at Library.FindOccurrences(String s, String f)
//       at Example.Main()
//
//       The Inner Exception:
//          Exception Name: ArgumentNullException
//          Message: Value cannot be null.
//    Parameter name: value
//
//          Stack Trace:
//          at System.String.IndexOf(String value, Int32 startIndex, Int32 count, Stri
//    ngComparison comparisonType)
//       at Library.FindOccurrences(String s, String f)
Try
    indexes = s.FindOccurrences(toFind)
    ShowOccurrences(s, toFind, indexes)
Catch e As ArgumentNullException
    Console.WriteLine("An exception ({0}) occurred.",
                   e.GetType().Name)
    Console.WriteLine("   Message: {1}{0}", vbCrLf, e.Message)
    Console.WriteLine("   Stack Trace:{0}   {1}{0}", vbCrLf, e.StackTrace)
    Dim ie As Exception = e.InnerException
    If ie IsNot Nothing Then
        Console.WriteLine("   The Inner Exception:")
        Console.WriteLine("      Exception Name: {0}", ie.GetType().Name)
        Console.WriteLine("      Message: {1}{0}", vbCrLf, ie.Message)
        Console.WriteLine("      Stack Trace:{0}   {1}{0}", vbCrLf, ie.StackTrace)
    End If
End Try
' The example displays the following output:
'       'a' occurs at the following character positions: 4, 7, 15
'
'       An exception (ArgumentNullException) occurred.
'          Message: You must supply a search string.
'
'          Stack Trace:
'             at Library.FindOccurrences(String s, String f)
'          at Example.Main()
'
'          The Inner Exception:
'             Exception Name: ArgumentNullException
'             Message: Value cannot be null.
'       Parameter name: value
'
'             Stack Trace:
'             at System.String.IndexOf(String value, Int32 startIndex, Int32 count, Stri
'       ngComparison comparisonType)
'          at Library.FindOccurrences(String s, String f)

Wybieranie standardowych wyjątków

Jeśli musisz zgłosić wyjątek, często można użyć istniejącego typu wyjątku na platformie .NET zamiast implementowania wyjątku niestandardowego. Należy użyć standardowego typu wyjątku w następujących dwóch warunkach:

  • Zgłaszasz wyjątek spowodowany błędem użycia (czyli błędem logiki programu wywoływanym przez dewelopera, który wywołuje metodę). Zazwyczaj zgłaszany jest wyjątek, taki jak ArgumentException, ArgumentNullException, InvalidOperationExceptionlub NotSupportedException. Ciąg podany w konstruktorze obiektu wyjątku podczas tworzenia wystąpienia obiektu wyjątku powinien opisać błąd, aby deweloper mógł go naprawić. Aby uzyskać więcej informacji, zobacz Message właściwość .

  • Obsługujesz błąd, który można przekazać do wywołującego przy użyciu istniejącego wyjątku platformy .NET. Należy zgłosić najbardziej możliwy wyjątek pochodny. Jeśli na przykład metoda wymaga, aby argument był prawidłowym elementem członkowskim typu wyliczenia, należy zgłosić (najbardziej pochodną klasę InvalidEnumArgumentException ) zamiast ArgumentException.

W poniższej tabeli wymieniono typowe typy wyjątków i warunki, w których można je zgłosić.

Wyjątek Stan
ArgumentException Argument inny niż null przekazywany do metody jest nieprawidłowy.
ArgumentNullException Argument przekazywany do metody to null.
ArgumentOutOfRangeException Argument znajduje się poza zakresem prawidłowych wartości.
DirectoryNotFoundException Część ścieżki katalogu jest nieprawidłowa.
DivideByZeroException Mianownik w klasie całkowitej lub Decimal operacji dzielenia wynosi zero.
DriveNotFoundException Dysk jest niedostępny lub nie istnieje.
FileNotFoundException Plik nie istnieje.
FormatException Wartość nie jest w odpowiednim formacie, który ma zostać przekonwertowany z ciągu za pomocą metody konwersji, takiej jak Parse.
IndexOutOfRangeException Indeks znajduje się poza granicami tablicy lub kolekcji.
InvalidOperationException Wywołanie metody jest nieprawidłowe w bieżącym stanie obiektu.
KeyNotFoundException Nie można odnaleźć określonego klucza dostępu do elementu członkowskiego w kolekcji.
NotImplementedException Metoda lub operacja nie jest zaimplementowana.
NotSupportedException Metoda lub operacja nie jest obsługiwana.
ObjectDisposedException Operacja jest wykonywana na obiekcie, który został usunięty.
OverflowException Operacja arytmetyczna, rzutowania lub konwersji powoduje przepełnienie.
PathTooLongException Długość ścieżki lub pliku przekracza maksymalną długość zdefiniowaną przez system.
PlatformNotSupportedException Operacja nie jest obsługiwana na bieżącej platformie.
RankException Tablica z nieprawidłową liczbą wymiarów jest przekazywana do metody.
TimeoutException Interwał czasu przydzielony do operacji wygasł.
UriFormatException Jest używany nieprawidłowy identyfikator URI (Uniform Resource Identifier).

Implementowanie wyjątków niestandardowych

W następujących przypadkach użycie istniejącego wyjątku platformy .NET do obsługi warunku błędu nie jest odpowiednie:

  • Gdy wyjątek odzwierciedla unikatowy błąd programu, którego nie można zamapować na istniejący wyjątek platformy .NET.

  • Jeśli wyjątek wymaga obsługi innej niż obsługa, która jest odpowiednia dla istniejącego wyjątku platformy .NET, lub wyjątek musi być niejednoznaczny z podobnego wyjątku. Jeśli na przykład zgłosisz ArgumentOutOfRangeException wyjątek podczas analizowania reprezentacji liczbowej ciągu, który jest poza zakresem docelowego typu całkowitego, nie chcesz używać tego samego wyjątku dla błędu, który wynika z obiektu wywołującego nie dostarcza odpowiednich wartości ograniczonych podczas wywoływania metody.

Klasa Exception jest klasą bazową wszystkich wyjątków na platformie .NET. Wiele klas pochodnych opiera się na odziedziczonym zachowaniu składowych Exception klasy; nie zastępują składowych Exceptionklasy ani nie definiują żadnych unikatowych składowych.

Aby zdefiniować własną klasę wyjątków:

  1. Zdefiniuj klasę dziedziczą z Exceptionklasy . W razie potrzeby zdefiniuj wszelkie unikatowe elementy członkowskie wymagane przez klasę, aby podać dodatkowe informacje o wyjątku. Na przykład ArgumentException klasa zawiera właściwość określającą ParamName nazwę parametru, którego argument spowodował wyjątek, a RegexMatchTimeoutException właściwość zawiera właściwość wskazującą MatchTimeout interwał limitu czasu.

  2. W razie potrzeby przesłoń wszystkie dziedziczone elementy członkowskie, których funkcjonalność chcesz zmienić lub zmodyfikować. Należy pamiętać, że większość istniejących klas pochodnych Exception nie zastępuje zachowania odziedziczonych elementów członkowskich.

  3. Ustal, czy niestandardowy obiekt wyjątku jest możliwy do serializacji. Serializacja umożliwia zapisywanie informacji o wyjątku i zezwala na udostępnianie informacji o wyjątkach przez serwer i serwer proxy klienta w kontekście komunikacji zdalnie. Aby obiekt wyjątku był serializowalny, oznacz go atrybutem SerializableAttribute .

  4. Zdefiniuj konstruktory klasy wyjątków. Zazwyczaj klasy wyjątków mają co najmniej jeden z następujących konstruktorów:

    • Exception(), który używa wartości domyślnych do inicjowania właściwości nowego obiektu wyjątku.

    • Exception(String), który inicjuje nowy obiekt wyjątku z określonym komunikatem o błędzie.

    • Exception(String, Exception), który inicjuje nowy obiekt wyjątku z określonym komunikatem o błędzie i wyjątkiem wewnętrznym.

    • Exception(SerializationInfo, StreamingContext), który jest konstruktorem protected , który inicjuje nowy obiekt wyjątku z serializowanych danych. Należy zaimplementować ten konstruktor, jeśli wybrano opcję serializacji obiektu wyjątku.

Poniższy przykład ilustruje użycie niestandardowej klasy wyjątków. Definiuje wyjątek NotPrimeException zgłaszany, gdy klient próbuje pobrać sekwencję liczb pierwszych, określając liczbę początkową, która nie jest podstawowa. Wyjątek definiuje nową właściwość , NonPrimektóra zwraca liczbę inną niż prime, która spowodowała wyjątek. Oprócz implementowania chronionego konstruktora bez parametrów i konstruktora z parametrami SerializationInfo i StreamingContext do serializacji NotPrimeException , klasa definiuje trzy dodatkowe konstruktory do obsługi NonPrime właściwości. Każdy konstruktor wywołuje konstruktor klasy bazowej oprócz zachowania wartości liczby innej niż prime. Klasa NotPrimeException jest również oznaczona atrybutem SerializableAttribute .

using System;
using System.Runtime.Serialization;

[Serializable()]
public class NotPrimeException : Exception
{
   private int notAPrime;

   protected NotPrimeException()
      : base()
   { }

   public NotPrimeException(int value) :
      base(String.Format("{0} is not a prime number.", value))
   {
      notAPrime = value;
   }

   public NotPrimeException(int value, string message)
      : base(message)
   {
      notAPrime = value;
   }

   public NotPrimeException(int value, string message, Exception innerException) :
      base(message, innerException)
   {
      notAPrime = value;
   }

   protected NotPrimeException(SerializationInfo info,
                               StreamingContext context)
      : base(info, context)
   { }

   public int NonPrime
   { get { return notAPrime; } }
}
namespace global

open System
open System.Runtime.Serialization

[<Serializable>]
type NotPrimeException = 
    inherit Exception
    val notAPrime: int

    member this.NonPrime =
        this.notAPrime

    new (value) =
        { inherit Exception($"%i{value} is not a prime number."); notAPrime = value }

    new (value, message) =
        { inherit Exception(message); notAPrime = value }

    new (value, message, innerException: Exception) =
        { inherit Exception(message, innerException); notAPrime = value }

    // F# does not support protected members
    new () = 
        { inherit Exception(); notAPrime = 0 }

    new (info: SerializationInfo, context: StreamingContext) =
        { inherit Exception(info, context); notAPrime = 0 }
Imports System.Runtime.Serialization

<Serializable()> _
Public Class NotPrimeException : Inherits Exception
   Private notAPrime As Integer

   Protected Sub New()
      MyBase.New()
   End Sub

   Public Sub New(value As Integer)
      MyBase.New(String.Format("{0} is not a prime number.", value))
      notAPrime = value
   End Sub

   Public Sub New(value As Integer, message As String)
      MyBase.New(message)
      notAPrime = value
   End Sub

   Public Sub New(value As Integer, message As String, innerException As Exception)
      MyBase.New(message, innerException)
      notAPrime = value
   End Sub

   Protected Sub New(info As SerializationInfo,
                     context As StreamingContext)
      MyBase.New(info, context)
   End Sub

   Public ReadOnly Property NonPrime As Integer
      Get
         Return notAPrime
      End Get
   End Property
End Class

Klasa pokazana PrimeNumberGenerator w poniższym przykładzie używa sita Eratosthenes do obliczania sekwencji liczb pierwszych z 2 do limitu określonego przez klienta w wywołaniu konstruktora klasy. Metoda GetPrimesFrom zwraca wszystkie liczby pierwsze, które są większe lub równe określonej niższej granicy, ale zwraca NotPrimeException wartość , jeśli ta niższa granica nie jest liczbą pierwszą.

using System;
using System.Collections.Generic;

[Serializable]
public class PrimeNumberGenerator
{
   private const int START = 2;
   private int maxUpperBound = 10000000;
   private int upperBound;
   private bool[] primeTable;
   private List<int> primes = new List<int>();

   public PrimeNumberGenerator(int upperBound)
   {
      if (upperBound > maxUpperBound)
      {
         string message = String.Format(
                           "{0} exceeds the maximum upper bound of {1}.",
                           upperBound, maxUpperBound);
         throw new ArgumentOutOfRangeException(message);
      }
      this.upperBound = upperBound;
      // Create array and mark 0, 1 as not prime (True).
      primeTable = new bool[upperBound + 1];
      primeTable[0] = true;
      primeTable[1] = true;

      // Use Sieve of Eratosthenes to determine prime numbers.
      for (int ctr = START; ctr <= (int)Math.Ceiling(Math.Sqrt(upperBound));
            ctr++)
      {
         if (primeTable[ctr]) continue;

         for (int multiplier = ctr; multiplier <= upperBound / ctr; multiplier++)
            if (ctr * multiplier <= upperBound) primeTable[ctr * multiplier] = true;
      }
      // Populate array with prime number information.
      int index = START;
      while (index != -1)
      {
         index = Array.FindIndex(primeTable, index, (flag) => !flag);
         if (index >= 1)
         {
            primes.Add(index);
            index++;
         }
      }
   }

   public int[] GetAllPrimes()
   {
      return primes.ToArray();
   }

   public int[] GetPrimesFrom(int prime)
   {
      int start = primes.FindIndex((value) => value == prime);
      if (start < 0)
         throw new NotPrimeException(prime, String.Format("{0} is not a prime number.", prime));
      else
         return primes.FindAll((value) => value >= prime).ToArray();
   }
}
namespace global

open System

[<Serializable>]
type PrimeNumberGenerator(upperBound) =
    let start = 2
    let maxUpperBound = 10000000
    let primes = ResizeArray()
    let primeTable = 
        upperBound + 1
        |> Array.zeroCreate<bool>

    do
        if upperBound > maxUpperBound then
            let message = $"{upperBound} exceeds the maximum upper bound of {maxUpperBound}."
            raise (ArgumentOutOfRangeException message)
        
        // Create array and mark 0, 1 as not prime (True).
        primeTable[0] <- true
        primeTable[1] <- true

        // Use Sieve of Eratosthenes to determine prime numbers.
        for i = start to float upperBound |> sqrt |> ceil |> int do
            if not primeTable[i] then
                for multiplier = i to upperBound / i do
                    if i * multiplier <= upperBound then
                        primeTable[i * multiplier] <- true
        
        // Populate array with prime number information.
        let mutable index = start
        while index <> -1 do
            index <- Array.FindIndex(primeTable, index, fun flag -> not flag)
            if index >= 1 then
                primes.Add index
                index <- index + 1

    member _.GetAllPrimes() =
        primes.ToArray()

    member _.GetPrimesFrom(prime) =
        let start = 
            Seq.findIndex ((=) prime) primes
        
        if start < 0 then
            raise (NotPrimeException(prime, $"{prime} is not a prime number.") )
        else
            Seq.filter ((>=) prime) primes
            |> Seq.toArray
Imports System.Collections.Generic

<Serializable()> Public Class PrimeNumberGenerator
   Private Const START As Integer = 2
   Private maxUpperBound As Integer = 10000000
   Private upperBound As Integer
   Private primeTable() As Boolean
   Private primes As New List(Of Integer)

   Public Sub New(upperBound As Integer)
      If upperBound > maxUpperBound Then
         Dim message As String = String.Format(
             "{0} exceeds the maximum upper bound of {1}.",
             upperBound, maxUpperBound)
         Throw New ArgumentOutOfRangeException(message)
      End If
      Me.upperBound = upperBound
      ' Create array and mark 0, 1 as not prime (True).
      ReDim primeTable(upperBound)
      primeTable(0) = True
      primeTable(1) = True

      ' Use Sieve of Eratosthenes to determine prime numbers.
      For ctr As Integer = START To CInt(Math.Ceiling(Math.Sqrt(upperBound)))
         If primeTable(ctr) Then Continue For

         For multiplier As Integer = ctr To CInt(upperBound \ ctr)
            If ctr * multiplier <= upperBound Then primeTable(ctr * multiplier) = True
         Next
      Next
      ' Populate array with prime number information.
      Dim index As Integer = START
      Do While index <> -1
         index = Array.FindIndex(primeTable, index, Function(flag)
                                                       Return Not flag
                                                    End Function)
         If index >= 1 Then
            primes.Add(index)
            index += 1
         End If
      Loop
   End Sub

   Public Function GetAllPrimes() As Integer()
      Return primes.ToArray()
   End Function

   Public Function GetPrimesFrom(prime As Integer) As Integer()
      Dim start As Integer = primes.FindIndex(Function(value)
                                                 Return value = prime
                                              End Function)
      If start < 0 Then
         Throw New NotPrimeException(prime, String.Format("{0} is not a prime number.", prime))
      Else
         Return primes.FindAll(Function(value)
                                  Return value >= prime
                               End Function).ToArray()
      End If
   End Function
End Class

Poniższy przykład wykonuje dwa wywołania GetPrimesFrom metody z liczbami innych niż prime, z których jeden przekracza granice domeny aplikacji. W obu przypadkach wyjątek jest zgłaszany i pomyślnie obsługiwany w kodzie klienta.

using System;
using System.Reflection;

class Example1
{
    public static void Main()
    {
        int limit = 10000000;
        PrimeNumberGenerator primes = new PrimeNumberGenerator(limit);
        int start = 1000001;
        try
        {
            int[] values = primes.GetPrimesFrom(start);
            Console.WriteLine("There are {0} prime numbers from {1} to {2}",
                              start, limit);
        }
        catch (NotPrimeException e)
        {
            Console.WriteLine("{0} is not prime", e.NonPrime);
            Console.WriteLine(e);
            Console.WriteLine("--------");
        }

        AppDomain domain = AppDomain.CreateDomain("Domain2");
        PrimeNumberGenerator gen = (PrimeNumberGenerator)domain.CreateInstanceAndUnwrap(
                                          typeof(Example).Assembly.FullName,
                                          "PrimeNumberGenerator", true,
                                          BindingFlags.Default, null,
                                          new object[] { 1000000 }, null, null);
        try
        {
            start = 100;
            Console.WriteLine(gen.GetPrimesFrom(start));
        }
        catch (NotPrimeException e)
        {
            Console.WriteLine("{0} is not prime", e.NonPrime);
            Console.WriteLine(e);
            Console.WriteLine("--------");
        }
    }
}
open System
open System.Reflection

let limit = 10000000
let primes = PrimeNumberGenerator limit
let start = 1000001
try
    let values = primes.GetPrimesFrom start
    printfn $"There are {values.Length} prime numbers from {start} to {limit}"
with :? NotPrimeException as e ->
    printfn $"{e.NonPrime} is not prime"
    printfn $"{e}"
    printfn "--------"

let domain = AppDomain.CreateDomain "Domain2"
let gen = 
    domain.CreateInstanceAndUnwrap(
        typeof<PrimeNumberGenerator>.Assembly.FullName,
        "PrimeNumberGenerator", true,
        BindingFlags.Default, null,
        [| box 1000000 |], null, null)
    :?> PrimeNumberGenerator
try
    let start = 100
    printfn $"{gen.GetPrimesFrom start}"
with :? NotPrimeException as e ->
    printfn $"{e.NonPrime} is not prime"
    printfn $"{e}"
    printfn "--------"
Imports System.Reflection

Module Example
   Sub Main()
      Dim limit As Integer = 10000000
      Dim primes As New PrimeNumberGenerator(limit)
      Dim start As Integer = 1000001
      Try
         Dim values() As Integer = primes.GetPrimesFrom(start)
         Console.WriteLine("There are {0} prime numbers from {1} to {2}",
                           start, limit)
      Catch e As NotPrimeException
         Console.WriteLine("{0} is not prime", e.NonPrime)
         Console.WriteLine(e)
         Console.WriteLine("--------")
      End Try

      Dim domain As AppDomain = AppDomain.CreateDomain("Domain2")
      Dim gen As PrimeNumberGenerator = domain.CreateInstanceAndUnwrap(
                                        GetType(Example).Assembly.FullName,
                                        "PrimeNumberGenerator", True,
                                        BindingFlags.Default, Nothing,
                                        {1000000}, Nothing, Nothing)
      Try
         start = 100
         Console.WriteLine(gen.GetPrimesFrom(start))
      Catch e As NotPrimeException
         Console.WriteLine("{0} is not prime", e.NonPrime)
         Console.WriteLine(e)
         Console.WriteLine("--------")
      End Try
   End Sub
End Module
' The example displays the following output:
'      1000001 is not prime
'      NotPrimeException: 1000001 is not a prime number.
'         at PrimeNumberGenerator.GetPrimesFrom(Int32 prime)
'         at Example.Main()
'      --------
'      100 is not prime
'      NotPrimeException: 100 is not a prime number.
'         at PrimeNumberGenerator.GetPrimesFrom(Int32 prime)
'         at Example.Main()
'      --------

Przykłady

W poniższym przykładzie pokazano catch blok (with w języku F#), który jest zdefiniowany do obsługi ArithmeticException błędów. Ten catch blok przechwytuje DivideByZeroException również błędy, ponieważ DivideByZeroException pochodzi z ArithmeticException i nie ma catch jawnie zdefiniowanego bloku dla DivideByZeroException błędów.

using System;

class ExceptionTestClass
{
   public static void Main()
   {
      int x = 0;
      try
      {
         int y = 100 / x;
      }
      catch (ArithmeticException e)
      {
         Console.WriteLine($"ArithmeticException Handler: {e}");
      }
      catch (Exception e)
      {
         Console.WriteLine($"Generic Exception Handler: {e}");
      }
   }	
}
/*
This code example produces the following results:

ArithmeticException Handler: System.DivideByZeroException: Attempted to divide by zero.
   at ExceptionTestClass.Main()

*/
module ExceptionTestModule

open System

let x = 0
try
    let y = 100 / x
    ()
with
| :? ArithmeticException as e ->
    printfn $"ArithmeticException Handler: {e}"
| e ->
    printfn $"Generic Exception Handler: {e}"

// This code example produces the following results:
//     ArithmeticException Handler: System.DivideByZeroException: Attempted to divide by zero.
//        at <StartupCode$fs>.$ExceptionTestModule.main@()
Class ExceptionTestClass
   
   Public Shared Sub Main()
      Dim x As Integer = 0
      Try
         Dim y As Integer = 100 / x
      Catch e As ArithmeticException
         Console.WriteLine("ArithmeticException Handler: {0}", e.ToString())
      Catch e As Exception
         Console.WriteLine("Generic Exception Handler: {0}", e.ToString())
      End Try
   End Sub
End Class
'
'This code example produces the following results:
'
'ArithmeticException Handler: System.OverflowException: Arithmetic operation resulted in an overflow.
'   at ExceptionTestClass.Main()
'