共用方式為


System.Exception 類別

本文提供此 API 參考文件的補充備註。

Exception 類別是所有例外狀況的基類。 發生錯誤時,系統或目前正在執行的應用程式會擲回包含錯誤相關信息的例外狀況來報告錯誤。 擲回例外狀況之後,它會由應用程式或默認例外狀況處理程序處理。

錯誤和例外狀況

執行時錯誤可能因多種原因發生。 不過,並非所有錯誤都應該在程式碼中當做例外狀況來處理。 以下是執行時可能發生的一些錯誤類別,以及適當的回應方式。

  • 使用錯誤。 使用錯誤代表程式邏輯中可能導致例外狀況的錯誤。 不過,錯誤應該不是透過例外狀況處理來解決,而是透過修改錯誤碼來解決。 例如,下列範例中 Object.Equals(Object) 方法的覆寫假設 obj 參數必須一律為非空。

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

    執行 NullReferenceExceptionobj 時所產生的 null 例外狀況,可以藉由在呼叫 Object.Equals 覆寫前,修改原始程式碼來明確測試 null,然後重新編譯來消除。 下列範例包含處理 null 自變數的更正原始程式碼。

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

    您可以使用 Debug.Assert 方法來識別偵錯組建中的使用錯誤,以及 Trace.Assert 方法來識別偵錯和發行組建中的使用錯誤,而不是使用例外狀況處理。 如需詳細資訊,請參閱受控代碼中的 斷言

  • 程序錯誤。 程式錯誤是指執行時的錯誤,無法透過撰寫無錯誤的程式碼來避免。

    在某些情況下,程式錯誤可能會反映預期或例程錯誤狀況。 在此情況下,您可能想要避免使用例外狀況處理來處理程序錯誤,並改為重試作業。 例如,如果使用者預期以特定格式輸入日期,您可以呼叫 DateTime.TryParseExact 方法來剖析日期字串,此方法會傳回 Boolean 值,指出剖析作業是否成功,而不是使用 DateTime.ParseExact 方法,如果日期字串無法轉換成 FormatException 值,則會擲回 DateTime 例外狀況。 同樣地,如果使用者嘗試開啟不存在的檔案,您可以先呼叫 File.Exists 方法來檢查檔案是否存在,如果不存在,請提示使用者是否要建立檔案。

    在其他情況下,程序錯誤會反映可在程式碼中處理的意外錯誤狀況。 例如,即使您已檢查確保檔案存在,它可能會在您開啟之前被刪除,或者可能會損毀。 在此情況下,嘗試藉由具現化 StreamReader 物件或呼叫 Open 方法來開啟檔案,可能會擲回 FileNotFoundException 例外狀況。 在這些情況下,您應該使用例外處理進行錯誤復原。

  • 系統失敗。 系統故障是指無法以程式化方式有效處理的執行時錯誤。 例如,如果 Common Language Runtime 無法配置額外的記憶體,任何方法都可能會擲回 OutOfMemoryException 例外狀況。 一般而言,系統失敗不會使用例外狀況處理來處理。 相反地,您可以使用 AppDomain.UnhandledException 之類的事件,並呼叫 Environment.FailFast 方法來記錄例外狀況資訊,並在應用程式終止之前通知用戶失敗。

Try/catch 區塊

Common Language Runtime 提供例外狀況處理模型,該模型是以例外狀況表示為物件,以及將程式代碼和例外狀況處理程式代碼分成 try 區塊和 catch 區塊。 可以有一或多個 catch 區塊,每個區塊都設計成處理特定類型的例外狀況,或一個區塊設計來攔截比另一個區塊更特定的例外狀況。

如果應用程式處理在應用程式程式代碼區塊執行期間發生的例外狀況,程式代碼必須放在 try 語句中,且稱為 try 區塊。 處理 try 區塊擲回例外狀況的應用程式程式代碼會放在 catch 語句中,並稱為 catch 區塊。 零個或多個 catch 區塊會與 try 區塊相關聯,而且每個 catch 區塊都包含一個類型篩選條件,可決定其處理的例外狀況類型。

try 區塊發生例外狀況時,系統會依應用程式程式代碼中出現的順序搜尋相關聯的 catch 區塊,直到找到處理例外狀況的 catch 區塊為止。 如果 catch 區塊的類型篩選指定 catch 或任何 T 衍生自的類型,則 T 區塊會處理類型 T 的例外狀況。 系統會在找到處理例外狀況的第一個 catch 區塊之後停止搜尋。 基於這個理由,在應用程式程式碼中,必須在處理基類型的 catch 區塊之前指定處理特定類型的 catch 區塊,如本節後面的範例所示。 最後指定處理 System.Exception 的 catch 區塊。

如果與目前 catch 區塊相關聯的 try 區塊都未處理例外狀況,而且目前 try 區塊會巢狀於目前呼叫中的其他 try 區塊內,則會搜尋與下一個封入 catch 區塊相關聯的 try 區塊。 如果找不到例外狀況的 catch 區塊,系統會搜尋目前呼叫中的先前巢狀層級。 如果目前呼叫中找不到例外狀況的 catch 區塊,則會將例外狀況傳遞至呼叫堆棧,而先前的堆疊框架則會搜尋處理例外狀況的 catch 區塊。 呼叫堆疊的搜尋會持續進行,直到例外狀況被處理,或直到呼叫堆疊上沒有其他堆疊框為止。 如果到達呼叫堆疊的頂端,但沒有找到處理例外狀況的 catch 區塊,則默認例外狀況處理程式會處理它,而且應用程式會終止。

F# try..with 運算式

F# 不會使用 catch 區塊。 相反地,發生的例外狀況會使用單一 with 區塊進行模式匹配。 由於這是表達式,而不是語句,所有路徑都必須傳回相同的類型。 若要深入瞭解,請參閱 試用...表示式

例外狀況類型功能

例外狀況類型支援下列功能:

  • 描述錯誤的人類可讀文字。 發生例外狀況時,運行時間會發出簡訊,告知使用者錯誤的性質,並建議解決問題的動作。 此文字訊息會保留在例外狀況物件的 Message 屬性中。 在建立例外狀況對象期間,您可以將文字字串傳遞至建構函式,以描述該特定例外狀況的詳細數據。 如果未將錯誤訊息自變數提供給建構函式,則會使用預設錯誤訊息。 如需詳細資訊,請參閱 Message 屬性 (Property)。

  • 當例外狀況被擲出時,呼叫堆疊的狀態。 StackTrace 屬性會帶有堆疊追蹤,可用來判斷錯誤在程式代碼中發生的位置。 堆疊追蹤會列出呼叫所在來源檔案中所有呼叫的方法和行號。

例外狀況類別屬性

Exception 類別包含一些屬性,可協助識別程式代碼位置、類型、說明檔,以及例外狀況的原因:StackTraceInnerExceptionMessageHelpLinkHResultSourceTargetSiteData

當兩個或多個例外狀況之間存在因果關聯性時,InnerException 屬性會維護這項資訊。 外部例外狀況是由於這個內部例外狀況而觸發的。 處理外部例外狀況的程式代碼可以使用先前內部例外狀況的資訊,更適當地處理錯誤。 例外狀況的補充資訊可以儲存為 Data 屬性中的索引鍵/值組集合。

在建立例外狀況對象期間傳遞至建構函式的錯誤訊息字串應該經過當地語系化,而且可以使用 ResourceManager 類別從資源檔提供。 如需本地化資源的詳細資訊,請參閱 建立附屬元件封裝和部署資源 主題。

若要為使用者提供例外狀況發生原因的詳細資訊,HelpLink 屬性可以保存說明檔的 URL(或 URN)。

Exception 類別會使用具有值0x80131500的 HRESULT COR_E_EXCEPTION

如需 Exception 類別實例的初始屬性值清單,請參閱 Exception 建構函式。

效能考量

擲回或處理例外狀況會耗用大量的系統資源和運行時間。 只拋出例外來處理真正特殊的狀況,而不是處理可預測的事件或流程控制。 例如,在某些情況下,例如當您開發類別函式庫時,如果方法的參數無效,則拋出例外是合理的,因為您的方法預期能接收到有效的參數。 如果不是使用錯誤的結果,則無效的方法自變數表示發生異常狀況。 相反地,如果使用者輸入無效,請勿擲回例外狀況,因為您可以預期用戶偶爾輸入無效的數據。 請改為提供重試機制,讓使用者可以輸入有效的輸入。 您也不應該使用例外狀況來處理使用錯誤。 請改用 斷言 來識別並更正使用錯誤。

此外,當傳回碼足夠時,請勿擲回例外狀況;請勿將傳回碼轉換成例外狀況;也請勿經常攔截例外狀況後忽略,然後繼續處理。

重新擲回例外狀況

在許多情況下,例外狀況處理程式只是想要將例外狀況傳遞給呼叫端。 這最常發生在:

  • 類別庫,接著會包裝對 .NET 類別庫或其他類別庫中方法的呼叫。

  • 遇到致命例外狀況的應用程式或函式庫。 例外狀況處理程式可以記錄例外狀況,然後重新擲回例外狀況。

重新擲回例外狀況的建議方式是在 C# 中使用 throw 語句、在 F# 中使用 reraise 函式,以及在 Visual Basic 中使用 Throw 語句,而不包含表達式。 這可確保當例外狀況傳播至呼叫端時,會保留所有呼叫堆棧資訊。 下列範例說明這點。 字串擴充方法 FindOccurrences會包裝對 String.IndexOf(String, Int32) 的一或多個呼叫,而不需事先驗證其自變數。

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

呼叫者接著會呼叫 FindOccurrences 兩次。 在第二次呼叫 FindOccurrences時,呼叫端會傳遞 null 做為搜尋字串,這會導致 String.IndexOf(String, Int32) 方法擲回 ArgumentNullException 例外狀況。 這個例外狀況是由 FindOccurrences 方法處理,並傳回給呼叫端。 由於 throw 語句未搭配表達式使用,因此範例的輸出會顯示呼叫堆疊已保留。

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 ({e.GetType().Name}) occurred.");
            Console.WriteLine($"Message:{Environment.NewLine}   {e.Message}{Environment.NewLine}");
            Console.WriteLine($"Stack Trace:{Environment.NewLine}   {e.StackTrace}{Environment.NewLine}");
        }
    }

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

相反地,如果使用這個語句重新擲回例外狀況:

throw e;
Throw e
raise e

...則不會保留完整呼叫堆疊,而此範例會產生下列輸出:

'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()

稍微繁瑣的替代方法是擲回新的例外狀況,並在內部例外狀況中保留原始例外狀況的呼叫堆棧資訊。 接著,呼叫端可以使用新例外狀況的 InnerException 屬性來擷取堆疊框架和其他原始例外狀況的相關信息。 在此情況下,throw 語句為:

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)

處理例外狀況的使用者程式代碼必須知道 InnerException 屬性包含原始例外狀況的相關信息,如下列例外狀況處理程式所示。

try
{
    indexes = s.FindOccurrences(toFind);
    ShowOccurrences(s, toFind, indexes);
}
catch (ArgumentNullException e)
{
    Console.WriteLine($"An exception ({e.GetType().Name}) occurred.");
    Console.WriteLine($"   Message:{Environment.NewLine}{e.Message}");
    Console.WriteLine($"   Stack Trace:{Environment.NewLine}   {e.StackTrace}");
    Exception ie = e.InnerException;
    if (ie != null)
    {
        Console.WriteLine("   The Inner Exception:");
        Console.WriteLine($"      Exception Name: {ie.GetType().Name}");
        Console.WriteLine($"      Message: {ie.Message}{Environment.NewLine}");
        Console.WriteLine($"      Stack Trace:{Environment.NewLine}   {ie.StackTrace}{Environment.NewLine}");
    }
}
// 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)

選擇標準例外狀況

當您必須擲回例外狀況時,通常可以在 .NET 中使用現有的例外狀況類型,而不是實作自定義例外狀況。 您應該在這兩個條件下使用標準例外狀況類型:

  • 您正在擲回一個例外狀況,這由使用錯誤引起(即呼叫您方法的開發人員在程式邏輯上出了錯)。 一般而言,您會擲回例外狀況,例如 ArgumentExceptionArgumentNullExceptionInvalidOperationExceptionNotSupportedException。 具現化例外狀況物件時提供給例外狀況物件的建構函式字串應該描述錯誤,讓開發人員可以修正錯誤。 如需詳細資訊,請參閱 Message 屬性 (Property)。

  • 您正在處理一個錯誤,可以透過現有的 .NET 異常向呼叫者傳達。 您應該擲回可能衍生最多的例外狀況。 例如,如果一個方法需要一個引數是列舉型別的有效成員,您應該拋出 InvalidEnumArgumentException(最派生類別),而不是 ArgumentException

下表列出常見的例外類型,以及您要拋出它們的條件。

例外 狀況
ArgumentException 傳遞至方法的非 Null 自變數無效。
ArgumentNullException 傳遞給方法的參數是 null
ArgumentOutOfRangeException 自變數超出有效值的範圍。
DirectoryNotFoundException 目錄路徑的一部分無效。
DivideByZeroException 整數或 Decimal 除法運算中的分母為零。
DriveNotFoundException 磁碟機無法使用或不存在。
FileNotFoundException 檔案不存在。
FormatException 值不是由轉換方法從字串轉換的適當格式,例如 Parse
IndexOutOfRangeException 索引超出陣列或集合的界限。
InvalidOperationException 方法呼叫在物件的目前狀態中無效。
KeyNotFoundException 找不到用於存取集合中成員的指定索引鍵。
NotImplementedException 尚未實作的方法或操作。
NotSupportedException 此方法或操作不被支援。
ObjectDisposedException 作業會在已處置的物件上執行。
OverflowException 算術、轉型或轉換運算會導致溢位。
PathTooLongException 路徑或檔名超過系統定義的長度上限。
PlatformNotSupportedException 目前平臺上不支援此作業。
RankException 維度數目錯誤的陣列會傳遞至方法。
TimeoutException 指派給作業的時間間隔已過期。
UriFormatException 使用無效的統一資源標識碼(URI)。

實作自定義例外狀況

在下列情況下,使用現有的 .NET 例外狀況來處理錯誤狀況並不夠:

  • 當例外狀況反映無法對應至現有 .NET 例外狀況的唯一程序錯誤時。

  • 當例外狀況需要不同於現有 .NET 例外狀況的處理方式時,或需要將此例外狀況與類似的例外狀況區分時。 例如,如果您在解析字串時,由於其數值表示超出目標整數類型的範圍而拋出 ArgumentOutOfRangeException 例外狀況,那麼在呼叫者呼叫方法時未提供適當限制值時,您就不會想使用同樣的例外狀況來處理這樣的錯誤。

Exception 類別是 .NET 中所有例外狀況的基類。 許多衍生類別依賴 Exception 類別成員的繼承行為;它們不會覆寫 Exception的成員,也不會定義任何唯一的成員。

若要定義您自己的例外狀況類別:

  1. 定義繼承自 Exception的類別。 如有必要,請定義類別所需的任何唯一成員,以提供例外狀況的其他資訊。 例如,ArgumentException 類別包含 ParamName 屬性,指定自變數造成例外狀況的參數名稱,而 RegexMatchTimeoutException 屬性包含指出超時時間間隔的 MatchTimeout 屬性。

  2. 如有必要,請覆寫您想要變更或修改其功能的任何繼承成員。 請注意,大多數現有的Exception衍生類別不會覆寫其繼承成員的行為。

  3. 判斷您的自定義例外狀況物件是否可串行化。 序列化可讓您儲存例外的相關資訊,並允許伺服器與用戶端代理在遠端處理環境中共用例外資訊。 若要使例外狀況物件可串行化,請使用 SerializableAttribute 屬性標示它。

  4. 定義例外狀況類別的建構函式。 一般而言,例外狀況類別具有下列一或多個建構函式:

    • Exception(),它會使用預設值來初始化新例外狀況對象的屬性。

    • Exception(String),它會使用指定的錯誤訊息,初始化新的例外狀況物件。

    • Exception(String, Exception),它會使用指定的錯誤訊息和內部例外狀況,初始化新的例外狀況物件。

    • Exception(SerializationInfo, StreamingContext),這是從串行化數據初始化新例外狀況物件的 protected 建構函式。 如果您選擇讓例外狀況物件可串行化,您應該實作此建構函式。

下列範例說明如何使用自定義例外狀況類別。 當用戶嘗試透過指定一個非質數的起始編號來擷取質數序列時,系統會擲回一個 NotPrimeException 例外狀況。 例外狀況會定義新屬性 NonPrime,傳回造成例外狀況的非質數。 除了實作受保護的無參數建構函式和具有串行化 SerializationInfoStreamingContext 參數的建構函式之外,NotPrimeException 類別還定義三個額外的建構函式來支援 NonPrime 屬性。 除了保留非質數的值之外,每個建構函式都會呼叫基類建構函式。 NotPrimeException 類別也會以 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

下列範例所示的 PrimeNumberGenerator 類別會使用 Eratosthenes 篩法,計算出質數序列,從 2 到客戶端在對類別建構子的呼叫中指定的上限。 GetPrimesFrom 方法會傳回大於或等於指定下限的所有質數,但如果下限不是質數,則會擲回 NotPrimeException

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

下列範例會對 GetPrimesFrom 方法進行兩次呼叫,其中使用的數字均為非質數,並且其中一次呼叫會跨越應用程式域界限。 在這兩種情況下,例外都會被丟出,並在客戶端代碼中成功處理。

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 {start} prime numbers from {limit} to {2}");
        }
        catch (NotPrimeException e)
        {
            Console.WriteLine($"{e.NonPrime} is not prime");
            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($"{e.NonPrime} is not prime");
            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()
'      --------

範例

下列範例示範被定義為處理 catch 錯誤的 withArithmeticException in F#)區塊。 這個 catch 區塊也會攔截 DivideByZeroException 錯誤,因為 DivideByZeroException 衍生自 ArithmeticException,而且沒有明確定義 catch 錯誤的 DivideByZeroException 區塊。

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()
'