C# 예외 처리

김민구·2025년 5월 27일
0

C#

목록 보기
22/31

1. 예외(Exception)란 무엇인가?

예외는 프로그램 실행 중에 발생하는 비정상적인 상황을 의미합니다. 예를 들어, 배열의 유효 범위를 벗어난 인덱스에 접근하거나, 0으로 숫자를 나누려 할 때 예외가 발생할 수 있습니다. 예외가 발생하면 해당 지점에서 프로그램의 정상적인 흐름이 중단되고, 별도의 처리가 없다면 프로그램이 비정상적으로 종료됩니다.

소스의 예시처럼, 크기가 3인 int 배열 arr에서 i가 3 이상일 때 arr[i]에 접근하려 하면 예외가 발생하며, 13번째 줄에서 예외가 발생했기 때문에 그 이후의 코드는 더 이상 실행되지 않고 프로그램이 종료됩니다.

2. try-catch를 이용한 예외 처리

C#에서는 try-catch 문을 사용하여 예외를 처리합니다.

  • try 블록: 예외가 발생할 가능성이 있는 코드를 작성하는 영역입니다.
  • catch 블록: try 블록 안에서 특정 타입의 예외가 발생했을 때 해당 예외를 잡아서 처리하는 영역입니다.

예를 들어, 배열 범위 초과 예외(IndexOutOfRangeException)를 처리하려면 다음과 같이 작성할 수 있습니다:

try
{
    // 예외 발생 가능성 있는 코드 (예: arr[i] 접근)
    Console.WriteLine(arr[i]); // i가 범위를 넘으면 여기서 예외 발생
}
catch (IndexOutOfRangeException e) // IndexOutOfRangeException 예외를 잡습니다.
{
    // 예외 발생 시 처리할 코드
    Console.WriteLine($"예외가 발생했습니다: {e.Message}");
}
// 예외가 잡히면 이 코드는 실행됩니다.
Console.WriteLine("종료");

소스에서처럼, try 블록 안에서 IndexOutOfRangeException이 발생하면 18번째 줄의 catch (IndexOutOfRangeException e) 블록이 이 예외를 잡아서 처리합니다. 예외가 처리되었으므로, 그 이후 코드인 "종료" 메시지도 정상적으로 출력됩니다.

하나의 try 블록에 여러 개의 catch 블록을 붙여서 다양한 종류의 예외를 다르게 처리할 수 있습니다. 가장 구체적인 예외 타입부터 먼저 catch하고, 마지막에 가장 일반적인 Exception 타입을 catch하는 것이 좋습니다. catch (Exception e)는 모든 종류의 예외를 잡을 수 있습니다.

3. System.Exception 클래스

C#의 모든 예외는 System.Exception 클래스를 상속받습니다. 이 클래스는 예외에 대한 정보를 담고 있으며, Message 속성은 예외에 대한 설명 메시지를 제공합니다. StackTrace 속성은 예외가 발생하기까지 호출된 메서드들의 순서(호출 스택)를 보여주어 문제의 원인을 파악하는 데 도움을 줍니다.

4. 예외 던지기 (throw)

프로그램 코드에서 특정 조건이 만족되지 않거나 잘못된 상황이 발생했을 때, 프로그래머가 직접 예외를 발생시킬 수 있습니다. 이를 예외 던지기(throw)라고 합니다. throw new Exception("예외 메시지");와 같이 사용합니다.

메서드 안에서 throw 문을 만나면 해당 메서드의 실행은 즉시 중단되고, 예외가 발생한 사실이 호출자에게 전달됩니다. 호출자는 이 예외를 try-catch 문으로 잡아서 처리할 수 있습니다. 소스의 DoSomething 메서드는 arg가 10보다 크면 예외를 던지고, Main 메서드에서 DoSomething을 호출한 후 이 예외를 catch 블록에서 잡습니다.

C# 7.0부터는 throw 문을 식의 일부로 사용할 수 있는 throw 식(Throw Expression) 기능이 추가되었습니다. 예를 들어 a ?? throw new ArgumentNullException();와 같이 사용하여 null 검사와 예외 발생을 간결하게 표현할 수 있습니다.

5. finally 블록

try-catch 문과 함께 finally 블록을 사용할 수 있습니다. finally 블록 안에 있는 코드는 예외 발생 여부나 catch 블록의 실행 여부에 관계없이 항상 실행됨을 보장합니다.

finally 블록은 주로 파일을 닫거나, 네트워크 연결을 끊거나, 데이터베이스 연결을 해제하는 등 자원을 정리하는 코드를 작성하는 데 사용됩니다. try 블록에서 예외가 발생하든 안 하든, 심지어 try 블록이나 catch 블록에서 return 문을 만나 메서드를 빠져나가더라도 finally 블록은 실행됩니다.

try
{
    // 예외 발생 가능성 있는 코드
}
catch (Exception e)
{
    // 예외 처리 코드
}
finally
{
    // 예외 발생 여부와 상관없이 항상 실행되는 코드
    // (예: 자원 해제)
}

소스는 예외가 발생했을 때 자원 해제 코드를 각 catch 블록마다 반복하는 비효율적인 상황을 보여주며, finally 블록이 이를 해결해준다는 것을 시사합니다.

!주의! finally 블록 안에서 또 다른 예외가 발생하면 어떻게 될까요? 소스에 따르면, finally 블록 안에서 발생한 예외는 처리되지 않은 예외가 되며, 이는 프로그램의 비정상 종료로 이어질 수 있습니다 (이는 소스에 직접 명시되지는 않았으나, 일반적인 C#의 예외 처리 동작입니다). 아주 중요한 자원 해제 코드가 실패할 가능성이 있다면, finally 안에서 또 다른 try-catch로 감싸는 것을 고려해볼 수 있지만, 이는 드문 경우에만 사용해야 합니다.

6. 사용자 정의 예외 클래스 만들기

.NET에서 제공하는 기본 예외 클래스 외에, 프로그램의 특정 오류 상황을 더 명확하게 나타내기 위해 개발자가 직접 예외 클래스를 정의할 수 있습니다. 사용자 정의 예외 클래스는 System.Exception 클래스를 상속받아 만듭니다.

class MyException : Exception // Exception 클래스 상속
{
    // ... 필요에 따라 생성자나 속성 등을 추가
}

사용자 정의 예외 클래스에 오류와 관련된 추가 정보(예: 잘못된 값, 허용 범위 등)를 담는 속성을 추가하면 예외 발생 시 문제 해결에 더 도움이 됩니다. 소스에서는 InvalidArgumentException 클래스에 ArgumentRange 속성을 추가한 예시를 보여주고, 소스에서는 이 예외를 던지고 잡아서 속성 값을 출력하는 코드를 보여줍니다.

7. 예외 필터링

C# 6.0부터는 catch 문에 when 키워드를 사용하여 예외 필터링을 적용할 수 있습니다. 예외 필터링을 사용하면 특정 조건이 만족될 때만 해당 catch 블록이 예외를 처리하도록 지정할 수 있습니다.

catch (FilterableException e) when (e.ErrorNo < 0) // 조건: ErrorNo가 0보다 작을 때만 처리
{
    // ... 처리 코드 ...
}
catch (FilterableException e) when (e.ErrorNo > 10) // 조건: ErrorNo가 10보다 클 때만 처리
{
    // ... 처리 코드 ...
}

소스는 FilterableExceptionErrorNo 속성 값에 따라 다른 catch 블록에서 처리하는 예시를 보여줍니다. 예외 필터링을 통해 예외 처리 로직을 더욱 세분화하고 가독성을 높일 수 있습니다.

8. 예외 처리 다시 생각하기

모든 비정상적인 상황에 대해 반드시 예외를 던져야 하는 것은 아닙니다. 소스에서는 나누기 연산에서 제수(divisor)가 0인 경우를 예로 들며, 예외를 던지는 대신 오류 코드를 반환하거나 (return -5;와 같이) out 매개변수를 사용하여 결과를 반환하고 성공 여부를 나타내는 방식을 고려해 볼 수 있다고 제시합니다. 제수 0처럼 발생 가능하고 예상 가능한 오류는 예외 처리 대신 다른 방식으로 처리하는 것이 더 적합할 수도 있다는 관점을 제시합니다. 하지만 배열 범위 초과와 같이 예상하기 어려운 오류는 예외 처리가 더 적합합니다.

profile
C#, Unity

0개의 댓글