System.Exception 類別

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

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

錯誤和例外狀況

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

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

    NullReferenceException由修改原始程式碼以在呼叫Object.Equals覆寫再重新編譯之前,明確測試 null,即可消除時objnull所發生的例外狀況。 下列範例包含處理 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: {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
    

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

  • 程序錯誤。 程序錯誤是一個運行時間錯誤,不一定可以藉由撰寫無 Bug 程式代碼來避免。

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

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

F# try..with expression

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

例外狀況類型功能

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

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

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

例外狀況類別屬性

類別Exception包含數個屬性,可協助識別程式碼位置、類型、說明檔,以及例外狀況的原因:StackTraceInnerExceptionHResultMessageTargetSiteHelpLinkSource和 。Data

當兩個或多個例外狀況之間存在因果關聯性時, 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 ({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()

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

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

選擇標準例外狀況

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

  • 您擲回因使用錯誤所造成的例外狀況(也就是呼叫方法之開發人員的程式邏輯發生錯誤)。 一般而言,您會擲回例外狀況,例如 ArgumentExceptionArgumentNullExceptionInvalidOperationExceptionNotSupportedException。 具現化例外狀況物件時提供給例外狀況物件的建構函式字串應該描述錯誤,讓開發人員可以修正錯誤。 如需詳細資訊,請參閱 Message 屬性 (Property)。

  • 您正在處理可與呼叫端通訊且具有現有 .NET 例外狀況的錯誤。 您應該擲回可能衍生最多的例外狀況。 例如,如果方法需要自變數是列舉型別的有效成員,您應該擲回 InvalidEnumArgumentException (衍生最多的類別),而不是 ArgumentException

下表列出常見的例外狀況類型,以及您要擲回這些例外狀況的條件。

例外狀況 Condition
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. 判斷您的自定義例外狀況物件是否可串行化。 串行化可讓您儲存例外狀況的相關信息,並允許伺服器和用戶端 Proxy 在遠端內容中共用例外狀況資訊。 若要使例外狀況物件可串行化,請使用 屬性標示它 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 的 Sieve,將質數序列從 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 {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()
'      --------

範例

下列範例示範catch已定義來處理ArithmeticException錯誤的 (with在 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()
'