[C#] 예외 처리

Kim Yuhyeon·2022년 3월 31일
0

게임개발

목록 보기
11/135

Exception 예외 처리

C#을 포함한 모든 .NET 프로그래밍 언어는
.NET Framework의 Exception 메커니즘에 따라 Exception을 처리한다.

.NET의 System.Exception은 모든 Exception의 Base 클래스이며,
예외 처리는 이 Exception 객체를 기본으로 처리하게 된다.

만약 Exception이 발생하였는데 이를 프로그램 내에서 처리하지 않으면 (이를 Unhandled Exception이라 부른다)
프로그램은 Crash하여 종료하게 된다.

C#에서는 try, catch, finally라는 키워드를 사용하여 Exception을 핸들링하게 되며, 또한 throw라는 C# 키워드를 통해 Exception을 만들어 던지거나
혹은 기존 Exception을 다시 던질 수 있다.

try
{
   // 실행하고자 하는 문장들
   DoSomething();
}
catch (Exception ex)
{
   // 에러 처리/로깅 등
   Log(ex);
   throw;
}

try-catch-finally

try

try 블럭은 실제 실행하고 싶은 프로그램 명령문들을 갖는 블럭이다.
만약 여기서 어떤 에러가 발생하면, 이는 catch 문에서 잡히게 된다.

catch

catch문은 모든 Exception을 일괄적으로 잡거나 혹은 특정 Exception을 선별하여 잡을 수 있다.

  • 모든 Exception을 잡고 싶을 때
catch { ... }

or

catch (Exception ex) { ... }

이처럼
모든 Exception의 베이스 클래스인 System.Exception를 잡으면 된다.

  • 특정 Exception을 잡고 싶을 때

해당 Exception Type을 catch하면 된다.
즉, Argument와 관련된 Exception을 잡고 싶으면,

catch (ArgumentException ex) { ... }

와 같이 잡게된다.

catch 블럭은 하나 혹은 여러 개 일 수 있다.
여러 catch를 사용하는 이유는 각 Exception 유형에 따라 서로 다른 에러 핸들링을 하기 위함이다.

finally

finally는 Exception이 발생했던 발생하지 않았던 상관없이
마지막에 반드시 실행되는 블럭이다.

예를 들어, try 블럭에서 SQL Connection객체를 만든 경우,
finally 블럭에서 Connection 객체의 Close() 메서드를 호출하면,
에러 발생 여부와 상관없이 항상 Connection 객체가 닫히게 된다.

예시

try
{
   //실행 문장들
}
catch (ArgumentException ex)
{
   // Argument 예외처리
}
catch (AccessViolationException ex)
{
   // AccessViolation 예외처리
}
finally
{
   // 마지막으로 실행하는 문장들
}

throw

try 블럭에서 Exception이 발생하였는데 이를 catch 문에서 잡었다면,
Exception은 이미 처리된 것으로 간주된다.

때때로 catch문에서 기존의 Exception을 다시 상위 호출자로 보내고 싶을 때가 있는데,
이때 throw 를 사용한다.

throw 문은 크게 3가지로 구별될 수 있다.

(1) throw 문 다음에 catch에서 전달받은 Exception 객체를 쓰는 경우

이는

throw ex;

와 같이 catch (Exception ex) 에서 전달받은 아규먼트 ex 를 사용하는 경우이다.

이러한 throw 방식은 ex 에 담긴 예외 정보를 보전하지만,
Stack Trace 정보를 다시 리셋하기 때문에
throw문 이전의 콜스택(Call Stack) 정보를 유실하게 된다.

따라서, 일반적으로 이러한 방식은 사용하지 않는 것이 좋다 !!

(2) throw 문 다음에 새 Exception 객체를 생성해서 전달하는 경우

새로운 Exception 객체를 만들어 던지기 위해서는

throw new MyException();

와 같이 C#의 new를 사용하여 새로운 Exception 객체를 만든 후,
이 객체를 throw 하면 된다.

이는 catch 에서 잡은 Exception을 Wrapping 하여
새로운 Exception을 전달할 때 사용하는데,
잘못 사용하면(InnerException을 포함하지 않으면) 기존 Exception 정보를 잃을 수 있다.

따라서, 이러한 방식을 사용하여 새로운 Exception 객체를 만들 때는
catch 에서 얻은 Exception 객체를 새 객체의 InnerException에 포함시켜
에러 정보를 보전하는 것이 좋다.

예를 들어,

throw new MyException(msg, ex); 

와 같이 catch에서 전달받은 ex를 InnerException으로 전달하는 것이 좋다.
InnerException의 StackTrace 속성은 어느 라인에서 에러가 발생했는지를 알려주는데,
이는 에러가 다른 메서드에서 발생했을 때는 물론 동일 메서드에서 발생했다 하더라도
정확히 어떤 라인에서 에러가 발생했는지를 알게 해 준다.

(3) throw 문 다음에 아무것도 없는 경우

throw; 

와 같이 뒤에 어떠한 Exception 객체 없이 그냥 throw문 만을 사용할 수 있는데,
이는 catch문에서 잡힌 Exception을 그대로 상위 호출 함수에게 전달하는 일(rethrow)
을 한다.

즉, 에러를 발생시킨 콜스택 정보를 그대로 상위 호출 함수에 전달하려면 이렇게 throw; 를 호출하면 된다.

한가지 주목할 점은,

  • throw; 는 에러가 다른 메서드에서 발생했을 때
    그 에러가 발생한 다른 메서드의 위치를 포함한다.
  • throw문 과 동일한 메서드에서 에러가 발생했을 때
    동일 메서드의 어느 라인에서 에러가 발생했는지는 포함하지 않는다.

예제

아래 예제는

IndexOutOfRangeException이 발생한 경우
MyException이라는 사용자 정의 Exception 객체를 만들어 던지게 하는데,
catch에서 잡은 ex 객체를 MyException의 InnerException에 포함시키고 있다.

FileNotFoundException이 발생한 경우는 throw ex 와 같이 호출하고 있는데, 이는 throw ex 까지의 이전 콜스택을 제거하기 때문에
throw ex 이후부터의 콜스택 정보만을 전달하게 된다.

마지막으로 그 외의 모든 Exception의 경우는 발생한 Exception을 그대로 상위 호출 함수에게 전달하기 위해 throw; 문을 사용하고 있다.

try
{
    // 실행 문장들
    Step1();
    Step2();
    Step3();
}
catch(IndexOutOfRangeException ex)
{
    // 새로운 Exception 생성하여 throw
    throw new MyException("Invalid index", ex);
}
catch(FileNotFoundException ex)
{    
    bool success = Log(ex);
    if (!success)
    {
        // 기존 Exception을 throw
        throw ex;
    }
}
catch(Exception ex)
{    
    Log(ex);
    // 발생된 Exception을 그대로 호출자에 전달
    throw;
}

예제

아래 예제는 SQL Server에 연결하여
데이터베이스 내의 테이블, 뷰 등의 SQL Objects 수를 가져온 후 이를 화면에 뿌리는 코드이다.

만약 SQLException 타입의 에러가 발생하면
catch 블럭에서 잡아서 에러 메시지만 콘솔에 표시하고 Exception을 삼키게 된다.

마지막의 finally 블럭은
SqlConnection의 Close() 메서드를 실행하여 Connection을 닫는다.
물론 에러가 발생하지 않더라도 finally 블럭은 실행되며, 따라서 SQL Connection은 항상 닫히게 된다.

string connStr = "Data Source=(local);Integrated Security=true;";
string sql = "SELECT COUNT(1) FROM sys.objects";
SqlConnection conn = null; 
try
{
    conn = new SqlConnection(connStr);
    conn.Open();
    SqlCommand cmd = new SqlCommand(sql, conn);
    object count = cmd.ExecuteScalar();
    Console.WriteLine(count);                
}
catch (SqlException ex)
{
    Console.WriteLine(ex.Message);
}
finally
{
    if (conn != null && 
        conn.State == System.Data.ConnectionState.Open)
    {
        conn.Close();
    }
}

💡 참고 포스팅

[예제로 배우는 C# 프로그래미밍] 기초 문법 - C# 예외 처리

0개의 댓글