다음을 통해 공유


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: {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 obj 때 발생하는 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 예외를 throw하는 DateTime.ParseExact 메서드를 사용하는 대신 구문 분석 작업이 성공했는지 여부를 나타내는 FormatException 값을 반환하는 DateTime 메서드를 호출하여 날짜 문자열을 구문 분석할 수 있습니다. 마찬가지로 사용자가 존재하지 않는 파일을 열려고 하면 먼저 File.Exists 메서드를 호출하여 파일이 있는지 확인하고 파일이 없는 경우 만들 것인지 묻는 메시지를 표시할 수 있습니다.

    다른 경우에 프로그램 오류는 코드에서 처리할 수 있는 예기치 않은 오류 조건을 반영합니다. 예를 들어 파일이 있는지 확인했더라도 파일을 열기 전에 삭제하거나 손상되었을 수 있습니다. 이 경우 StreamReader 개체를 인스턴스화하거나 Open 메서드를 호출하여 파일을 열려고 하면 FileNotFoundException 예외가 발생할 수 있습니다. 이러한 경우 예외 처리를 사용하여 오류에서 복구해야 합니다.

  • 시스템 오류. 시스템 오류는 의미 있는 방식으로 프로그래밍 방식으로 처리할 수 없는 런타임 오류입니다. 예를 들어 공용 언어 런타임에서 추가 메모리를 할당할 수 없는 경우 모든 메서드가 OutOfMemoryException 예외를 throw할 수 있습니다. 일반적으로 시스템 오류는 예외 처리를 사용하여 처리되지 않습니다. 대신 AppDomain.UnhandledException 같은 이벤트를 사용하고 Environment.FailFast 메서드를 호출하여 예외 정보를 기록하고 애플리케이션이 종료되기 전에 사용자에게 오류를 알릴 수 있습니다.

블록 시도/catch

공용 언어 런타임은 예외를 개체로 표현하고 프로그램 코드와 예외 처리 코드를 try 블록 및 catch 블록으로 분리하는 것을 기반으로 하는 예외 처리 모델을 제공합니다. 각각 특정 유형의 예외를 처리하도록 설계된 하나 이상의 catch 블록 또는 다른 블록보다 더 구체적인 예외를 catch하도록 설계된 하나의 블록이 있을 수 있습니다.

애플리케이션이 애플리케이션 코드 블록을 실행하는 동안 발생하는 예외를 처리하는 경우 코드는 try 문 내에 배치되어야 하며 try 블록이라고 합니다. try 블록에서 throw된 예외를 처리하는 애플리케이션 코드는 catch 문 내에 배치되며 catch 블록이라고 합니다. 0개 이상의 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 속성을 참조하세요.

  • 예외가 발생했을 때의 호출 스택 상태입니다. StackTrace 속성은 코드에서 오류가 발생하는 위치를 확인하는 데 사용할 수 있는 스택 추적을 전달합니다. 스택 추적에는 호출된 모든 메서드와 호출이 이루어지는 원본 파일의 줄 번호가 나열됩니다.

예외 클래스 속성

Exception 클래스에는 코드 위치, 형식, 도움말 파일 및 예외 이유를 식별하는 데 도움이 되는 여러 속성이 포함되어 있습니다. StackTrace, InnerException, Message, HelpLink, HResult, Source, TargetSiteData.

둘 이상의 예외 간에 인과 관계가 있는 경우 InnerException 속성은 이 정보를 유지 관리합니다. 외부 예외는 이 내부 예외에 대한 응답으로 발생합니다. 외부 예외를 처리하는 코드는 이전 내부 예외의 정보를 사용하여 오류를 보다 적절하게 처리할 수 있습니다. 예외에 대한 추가 정보는 Data 속성에 키/값 쌍의 컬렉션으로 저장할 수 있습니다.

예외 개체를 만드는 동안 생성자에 전달되는 오류 메시지 문자열은 지역화되어야 하며 ResourceManager 클래스를 사용하여 리소스 파일에서 제공할 수 있습니다. 지역화된 리소스에 대한 자세한 내용은 위성 어셈블리 만들기리소스 패키징 및 배포 항목을 참조하세요.

사용자에게 예외가 발생한 이유에 대한 광범위한 정보를 제공하기 위해 HelpLink 속성은 도움말 파일에 대한 URL(또는 URN)을 보유할 수 있습니다.

Exception 클래스는 HRESULT COR_E_EXCEPTION, 값은 0x80131500, 를 사용합니다.

Exception 클래스 인스턴스의 초기 속성 값 목록은 Exception 생성자를 참조하세요.

성능 고려 사항

예외를 throw하거나 처리하면 상당한 양의 시스템 리소스와 실행 시간이 사용됩니다. 예측 가능한 이벤트 또는 흐름 제어를 처리하지 않고 진정으로 특별한 조건을 처리하기 위해서만 예외를 throw합니다. 예를 들어 클래스 라이브러리를 개발할 때와 같은 경우에 메서드 인수가 유효하지 않은 경우 유효한 매개 변수를 사용하여 메서드를 호출해야 하므로 예외를 throw하는 것이 합리적입니다. 잘못된 메서드 인수가 사용 오류의 결과가 아닌 경우 특별한 문제가 발생했음을 의미합니다. 반대로, 사용자가 잘못된 데이터를 입력할 것으로 예상할 수 있으므로 사용자 입력이 잘못된 경우 예외를 throw하지 마세요. 대신 사용자가 유효한 입력을 입력할 수 있도록 재시도 메커니즘을 제공합니다. 예외를 사용하여 사용 오류를 처리해서는 안 됩니다. 대신 어설션 사용하여 사용 오류를 식별하고 수정합니다.

또한 반환 코드가 충분한 경우 예외를 throw하지 마세요. 반환 코드를 예외로 변환하지 마세요. 정기적으로 예외를 catch하여 무시하고 처리를 계속하지 마세요.

예외를 다시 발생시키기

대부분의 경우 예외 처리기는 예외를 호출자에게 전달하려고 합니다. 이 문제는 다음에서 가장 자주 발생합니다.

  • .NET 클래스 라이브러리 또는 다른 클래스 라이브러리의 메서드에 대한 호출을 차례로 래핑하는 클래스 라이브러리입니다.

  • 치명적인 예외가 발생하는 애플리케이션 또는 라이브러리입니다. 예외 처리기는 예외를 기록한 다음 예외를 다시 throw할 수 있습니다.

예외를 다시 throw하는 권장 방법은 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)

표준 예외 선택

예외를 throw해야 하는 경우 사용자 지정 예외를 구현하는 대신 .NET에서 기존 예외 형식을 사용할 수 있습니다. 다음 두 가지 조건에서 표준 예외 형식을 사용해야 합니다.

  • 사용 오류(즉, 메서드를 호출하는 개발자가 만든 프로그램 논리의 오류)로 인해 예외를 발생시킵니다. 일반적으로 ArgumentException, ArgumentNullException, InvalidOperationException또는 NotSupportedException같은 예외를 throw합니다. 예외 개체를 인스턴스화할 때 예외 개체의 생성자에 제공하는 문자열은 개발자가 오류를 수정할 수 있도록 오류를 설명해야 합니다. 자세한 내용은 Message 속성을 참조하세요.

  • 기존 .NET 예외를 사용하여 호출자에게 전달할 수 있는 오류를 처리하고 있습니다. 가능한 가장 파생된 예외를 던져야 합니다. 예를 들어 메서드에서 인수가 열거형 형식의 유효한 멤버여야 하는 경우 InvalidEnumArgumentException아닌 ArgumentException(가장 파생된 클래스)를 throw해야 합니다.

다음 표에서는 일반적인 예외 유형 및 예외를 throw할 조건을 나열합니다.

예외 조건
ArgumentException 메서드에 전달되는 null이 아닌 인수가 잘못되었습니다.
ArgumentNullException 메서드에 전달되는 인수는 null.
ArgumentOutOfRangeException 인수가 유효한 값 범위를 벗어났습니다.
DirectoryNotFoundException 디렉터리 경로의 일부가 잘못되었습니다.
DivideByZeroException 정수 또는 Decimal 나누기 연산의 분모는 0입니다.
DriveNotFoundException 드라이브를 사용할 수 없거나 존재하지 않습니다.
FileNotFoundException 파일이 없습니다.
FormatException 값은 Parse같은 변환 메서드에 의해 문자열에서 변환할 적절한 형식이 아닙니다.
IndexOutOfRangeException 인덱스가 배열 또는 컬렉션의 범위를 벗어났습니다.
InvalidOperationException 개체의 현재 상태에서 메서드 호출이 잘못되었습니다.
KeyNotFoundException 컬렉션의 멤버에 액세스하기 위해 지정된 키를 찾을 수 없습니다.
NotImplementedException 메서드 또는 작업이 구현되지 않습니다.
NotSupportedException 메서드 또는 작업은 지원되지 않습니다.
ObjectDisposedException 삭제된 개체에 대해 작업이 수행됩니다.
OverflowException 산술, 캐스팅 또는 변환 연산으로 인해 오버플로가 발생합니다.
PathTooLongException 경로 또는 파일 이름이 시스템 정의 최대 길이를 초과합니다.
PlatformNotSupportedException 현재 플랫폼에서는 작업이 지원되지 않습니다.
RankException 잘못된 차원 수를 가진 배열이 메서드에 전달됩니다.
TimeoutException 작업에 할당된 시간 간격이 만료되었습니다.
UriFormatException 잘못된 URI(Uniform Resource Identifier)가 사용됩니다.

사용자 지정 예외 구현

다음 경우 기존 .NET 예외를 사용하여 오류 조건을 처리하는 것은 적절하지 않습니다.

  • 예외가 기존 .NET 예외에 매핑할 수 없는 고유한 프로그램 오류를 반영하는 경우

  • 예외에 기존 .NET 예외에 적합한 처리와 다른 처리가 필요하거나 유사한 예외에서 예외를 명확하게 구분해야 하는 경우 예를 들어, 대상 정수 형식의 범위를 벗어난 문자열을 숫자로 변환할 때 ArgumentOutOfRangeException 예외를 발생시키는 경우, 메서드를 호출할 때 호출자가 적절한 제한된 값을 제공하지 않아 발생하는 오류에 대해 같은 예외를 사용하지 않아야 합니다.

Exception 클래스는 .NET에서 모든 예외의 기본 클래스입니다. 많은 파생 클래스는 Exception 클래스의 멤버의 상속된 동작을 사용합니다. Exception멤버를 재정의하지 않으며 고유한 멤버를 정의하지도 않습니다.

고유한 예외 클래스를 정의하려면 다음을 수행합니다.

  1. Exception상속되는 클래스를 정의합니다. 필요한 경우 예외에 대한 추가 정보를 제공하기 위해 클래스에 필요한 고유 멤버를 정의합니다. 예를 들어 ArgumentException 클래스에는 인수로 인해 예외가 발생한 매개 변수의 이름을 지정하는 ParamName 속성이 포함되며, RegexMatchTimeoutException 속성에는 제한 시간 간격을 나타내는 MatchTimeout 속성이 포함됩니다.

  2. 필요한 경우 변경하거나 수정하려는 기능을 가진 상속된 멤버를 재정의하십시오. 대부분의 기존 파생 클래스 Exception는 상속된 멤버의 동작을 재정의하지 않는다는 점을 유의하세요.

  3. 사용자 지정 예외 개체를 직렬화할 수 있는지 여부를 확인합니다. Serialization을 사용하면 예외에 대한 정보를 저장할 수 있으며 원격 컨텍스트에서 서버 및 클라이언트 프록시에서 예외 정보를 공유할 수 있습니다. 예외 개체를 직렬화할 수 있도록 하려면 SerializableAttribute 특성으로 표시합니다.

  4. 예외 클래스의 생성자를 정의합니다. 일반적으로 예외 클래스에는 다음 생성자 중 하나 이상이 있습니다.

    • Exception()- 기본값을 사용하여 새 예외 개체의 속성을 초기화합니다.

    • Exception(String)지정한 오류 메시지를 사용하여 새 예외 개체를 초기화합니다.

    • Exception(String, Exception)지정한 오류 메시지와 내부 예외를 사용하여 새 예외 개체를 초기화합니다.

    • Exception(SerializationInfo, StreamingContext)- 직렬화된 데이터에서 새 예외 개체를 초기화하는 protected 생성자입니다. 예외 개체를 직렬화할 수 있도록 선택한 경우 이 생성자를 구현해야 합니다.

다음 예제에서는 사용자 지정 예외 클래스의 사용을 보여 줍니다. 클라이언트가 소수가 아닌 시작 번호를 지정하여 소수 시퀀스를 검색하려고 할 때 throw되는 NotPrimeException 예외를 정의합니다. 예외는 예외를 발생시킨 소수가 아닌 숫자를 반환하는 새 속성 NonPrime정의합니다. serialization에 대한 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 throw합니다.

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 오류를 처리하도록 정의된 with(F#의ArithmeticException) 블록을 보여 줍니다. 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()
'