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
    

    可以在调用 Object.Equals 重写并重新编译之前,修改源代码以显式测试 null,从而消除 objnull 时引发的 NullReferenceException 异常。 以下示例包含处理 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
    

    可以使用 Debug.Assert 方法来识别调试版本中的使用错误,以及使用 Trace.Assert 方法识别调试和发布版本中的使用情况错误的,而不是使用异常处理来识别使用错误。 有关详细信息,请参阅托管代码中的断言

  • 程序错误。 程序错误是一个运行时错误,不一定能通过编写无 bug 代码来避免。

    在某些情况下,程序错误可能会反映预期或例程错误情况。 在这种情况下,可能需要避免使用异常处理来处理程序错误,而是重试该操作。 例如,如果用户预期以特定格式输入日期,则可以通过调用 DateTime.TryParseExact 方法来分析日期字符串,该方法返回一个 Boolean 值,指示分析操作是否成功,而不是使用 DateTime.ParseExact 方法,如果日期字符串无法转换为 DateTime 值,则该方法会引发 FormatException 异常。 同样,如果用户尝试打开不存在的文件,可以先调用 File.Exists 方法检查文件是否存在,如果不存在,则提示用户是否要创建该文件。

    在其他情况下,程序错误反映可以在代码中处理的意外错误情况。 例如,即使已经检查确保文件存在,文件也有可能在打开前被删除,或者可能已损坏。 在这种情况下,尝试通过实例化 StreamReader 对象或调用 Open 方法来打开文件可能会引发 FileNotFoundException 异常。 在这些情况下,应使用异常处理来从错误中恢复。

  • 系统故障。 系统故障是一个运行时错误,无法以有意义的方式进行编程处理。 例如,如果公共语言运行时无法分配额外的内存,则任何方法都可能引发 OutOfMemoryException 异常。 通常不使用异常处理来处理系统故障。 相反,你可以使用事件(例如 AppDomain.UnhandledException)并调用 Environment.FailFast 方法记录异常信息,并在应用程序终止之前将故障通知用户。

try/catch 块

公共语言运行时提供了一个异常处理模型,该模型基于将异常表示为对象,并将程序代码和异常处理代码分离到 try 块和 catch 块中。 可以有一个或多个 catch 块,每个块都专用于处理特定类型的异常,或者有一个块专用于捕获比另一个块更具体的异常。

如果应用程序处理在执行应用程序代码块期间发生的异常,则必须将代码置于 try 语句中,并称其为 try 块。 处理 try 块引发的异常的应用程序代码应放置在 catch 语句中,称为 catch 块。 零个或多个 catch 块与 try 块相关联,每个 catch 块都包含一个类型筛选器,用于确定它处理的异常类型。

try 块中发生异常时,系统会按照在应用程序代码中出现的顺序搜索关联的 catch 块,直到找到处理该异常的 catch 块。 如果 catch 块的类型筛选器指定 TT 派生自的任何类型,则 catch 块将处理类型 T 的异常。 系统会在找到第一个处理异常的 catch 块后停止搜索。 因此,在应用程序代码中,必须在处理其基类型的 catch 块之前指定处理类型的 catch 块,如本节后面的示例所示。 最后指定处理 System.Exception 的 catch 块。

如果与当前 try 块关联的 catch 块都不处理该异常,且当前的 try 块嵌套在当前调用中的其他 try 块中,则会搜索与下一个封闭 try 块关联的 catch 块。 如果找不到该异常的 catch 块,系统将在当前调用中搜索以前的嵌套级别。 如果在当前调用中找不到该异常的 catch 块,则会将异常向上传递调用堆栈,并在上一个堆栈帧搜索处理异常的 catch 块。 调用堆栈的搜索将继续执行,直到异常被处理或调用堆栈上没有更多帧。 如果到达调用堆栈的顶部时还未找到处理该异常的 catch 块,则默认异常处理程序将处理它,应用程序将终止。

F# try..with 表达式

F# 不使用 catch 块。 相反,引发的异常是使用单个 with 块匹配的模式。 由于这是表达式而不是语句,因此所有路径必须返回相同的类型。 若要了解详细信息,请参阅 try...with 表达式

异常类型功能

异常类型支持以下功能:

  • 描述错误的可读文本。 发生异常时,运行时会发出一条文本消息,告知用户错误的性质并提出解决问题的建议操作。 此文本消息保存在异常对象的 Message 属性中。 在创建异常对象期间,可以将文本字符串传递给构造函数,以描述该特定异常的详细信息。 如果未向构造函数提供错误消息参数,则使用默认错误消息。 有关更多信息,请参见 Message 属性。

  • 引发异常时调用堆栈的状态。 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 ({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 属性。

  • 你正在处理一个错误,该错误可使用现有 .NET 异常传达给调用方。 应引发派生程度最高的异常。 例如,如果一个方法需要参数是枚举类型的有效成员,则应引发 InvalidEnumArgumentException(派生程度最高的类)而不是 ArgumentException

下表列出了常见异常类型和需要引发异常的情况。

Exception 条件
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. 定义异常类的构造函数。 通常,异常类具有以下一个或多个构造函数:

下面的示例演示了自定义异常类的使用。 它定义了一个 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 类使用埃拉托色尼斯筛法计算从 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(在 F# 中为 with)块,该块定义用于处理 ArithmeticException 错误。 catch 块还会捕获 DivideByZeroException 错误,因为 DivideByZeroException 派生自 ArithmeticException 并且没有针对 DivideByZeroException 错误显式定义的 catch 块。

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