본문 바로가기

C# 공부/C# 기본 문법

예외 처리하기 (try~catch문, System.Exception 클래스, 예외 던지기, finally, 사용자 정의 예외클래스, 예외 필터(when), 예외처리의 장점)

A. try ~ catch문

 static void Main(string[] args)
 {
     int[] arr = { 1, 2, 3 };
     try
     {
         for (int i = 0; i < 5; i++)
             Console.WriteLine(arr[i]);
     }
     catch(IndexOutOfRangeException e)
     {
         Console.WriteLine($"예외가 발생했습니다 :{e.Message}");
     }
     Console.WriteLine("종료");
 }

 

B. System.Exception 클래스

C#에서 모든 예외클래스는 System.Exception 클래스로부터 파생되었다.

즉, catch절에서 Exception으로 모든 예외를 다 받아낼 수 있다.

* 하지만, 예외 상황에 따라 섬세한 예외 처리가 필요한 코드에서는 Exception 클래스만으로는 대응이 어려우니 무조건 Exception클래스를 사용하는 것은 금물. (처리하지 않아야 할 예외까지 처리하게되므로 버그 발생요인이된다.)

 

C. 예외 던지기

1. 메소드안에서 throw 하고 호출하는 try catch문에서 받아내는경우

static void DoSomething(int arg)
{
    if(arg<10)
        Console.WriteLine($"arg : {arg}");
    else
        throw.new Exception("arg가 10보다 큽니다.");
}

static void Main()
{
    try
    {
    	DoSomething(13);
    }
    catch(Exception e)
    {
    	Console.WriteLine(e.Message);
    }
}

 

2. 식(Expression)으로 예외처리하는경우

Throw는 보통 문(statement)로 처리하지만, C# 7.0부터는 식(expression)으로도 사용할 수 있도록 개선되었다.

static void Main(string[] args)
{
    try
    {
        int? a = null;
        int b = a ?? throw new ArgumentNullException(); // a==null이면 a리턴, a!=null 이면 throw리턴
    } 
    catch(ArgumentNullException e)
    {
        Console.WriteLine(e);
    }

    try
    {
        int[] array = new[] { 1, 2, 3 };
        int index = 4;
        int value = array[index >= 0 && index < 3 ? index : throw new IndexOutOfRangeException()];
    }
    catch (IndexOutOfRangeException e)
    {
        Console.WriteLine(e);
    }
}

 

D. try~catch 와 finally

try블록에서 코드를 실행하다 예외가 던져지면 catch절로 바로 넘어가므로 try블록의 자원해제같은 중요코드를 실행하지 못하게되므로 버그를 만드는 원인이된다. 예를들어 try블록 끝의 DB 커넥션을 못닫게되면 사용할 수 있는 커넥션이 점점줄어서 DB에 연결할수 없는 상황이 생긴다.

이런 상황을 해결하기위해 finally 절을 사용해서 뒷정리 코드를 넣는다.

static int Divide(int divisor, int dividend)
{
    try
    {
        Console.WriteLine("Divide() 시작");
        return divisor / dividend;
    }
    
    catch(DivideByZeryException e)
    {
    	throw e;
    }
    
    finally
    {
    	Console.WriteLine("Divide() 끝");
    }
}

* finally 안에서도 예외가 발생할수있으므로, 예외가 일어나지않는다고 100% 확신이 없으면 try~catch 블록을 한번더 사용한다.

 

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

특별한 데이터를 담아서 예외 처리 루틴에 추가정보를 제공하는 경우나, 예외상황을 더 잘 설명하고싶은경우에 사용자 정의 예외 클래스가 필요하다. 이 때, C#의 모든 예외는 Exception클래스를 상속받아야 한다.

class InvalidArgumentException : Exception
{
	public InvalidArgumentException()
    {
    }
    
    public InvalidArgumentException(string message) : base(message)
    {
    }
}

 

F. 예외 필터(Exception Filter)하기

C#6.0 부터는 catch절이 받아들일 예외 객체에 제약 사항을 명시하여 해당 조건을 만족하는 예외 객체에 대해서만 예외처리코드를 실행할 수 있게하는 예외 필터(Exception Filter)가 도입되었다.

catch() 문 뒤에 when 키워드를 이용해서 제약조건을 기술하면된다. (when을 if라고 생각하면된다.)

 class FilterableException : Exception
 {
     public int ErrorNo { get; set; }
 }
 class MainApp
 {
     static void Main(string[] args)
     {
         Console.WriteLine("Enter Number Between 0-10");
         string input = Console.ReadLine();
         try
         {
             int num = Int32.Parse(input);
             if (num < 0 || num > 10)
                 throw new FilterableException() { ErrorNo = num };
             else
                 Console.WriteLine($"Output : {num}");
         }
         catch(FilterableException e) when (e.ErrorNo<0) //ErrorNo 프로퍼티가 0미만인 경우만 catch하도록 예외 필터하기
         {
             Console.WriteLine("Negative input is not allowed");
         }
         catch(FilterableException e)when (e.ErrorNo>10) //ErrorNo 프로퍼티가 10초과인 경우만 catch하도록 예외 필터하기
         {
             Console.WriteLine("Too big number is not allowed");
         }
     }

 

 

G. 예외 처리의 장점

1. 실제 일을하는 코드와 문제를 처리하는 코드를 분리시킴으로써 코드를 간결하게 만들어준다.

2. 예외 객체의 StackTrace 프로퍼티를 통해 문제가 발생한 코드의 위치를 알려주므로 디버깅에 용이하다.

catch(DevidedByZeryException e)
{
    Console.WriteLine(e.StackTrace);
}
//실행결과
// 위치 : StackTrace.MainApp.Main(String[] args) 파일 : D:\StackTrace\MainApp.cs 줄 : 12

3. 문제점을 하나로 묶어내거나 코드에서 발생할 수 있는 오류를 종류별로 정리해주는 효과가있다. (가독성 up)