예외는 프로그램을 실행했을 때 비정상적으로 종료하는 것을 뜻한다.
예외가 발생하면 개발자는 예외 메시지로부터 오류의 원인을 찾을 수 있다.
CLR에 의해 전달되는 예외는 그 자체도 타입(Type)의 인스턴스다.
예를 들어 system.NullReferenceException과 System.IndexOutOfRangeException 예외는 클래스의 이름이고, 이 두 예외는 BCL의 하나인 mscorlib.dll에 정의 되어 있다.
직접 예외 타입을 만든느 것도 가능하며 조건은 System.Exception을 상속받는 것이다. 추가적인 관례는 다음과 같다.
예외가 발생한 경우 CLR의 기본 처리 과정은 예외 메시지를 출력하고 프로그램을 종료하는 것이다. 하지만 이는 개발자가 의도한 동작이 아닐 수 있다. 대부분의 경우 프로그램이 강제로 종료되기보다는 예외 상황을 사용자에게 알리고 프로그램은 여전히 계속 실행되기를 원할 것이다. 그걸 해결하기 위해 예외가 발생할 수 있는 코드를 미리 try/catch로 묶어 둬야 한다.
int div = 0;
try
{
int temp = 10 / div;
Console.WriteLine("try test");//여기까지 실행 안된다.
}
catch
{
Console.WriteLine("catch");//"catch"가 콘솔창에 뜬다.
}
위의 예시를 보면 0으로 나누었기 때문에 System.DivideByZeroException을 발생시키지만 try/catch를 통해 예외를 처리하겠다고 지정했으므로 프로그램이 예외로 인해 종료되지 않고 catch블럭으로 넘어가게 된다. 이때, 에러 뒷부분은 따로 실행되지 않기 때문에 에러가 발생해도 실행시키고 싶은 코드가 있다면 finally구문을 사용하자
try
{
int temp = 10 / div;
Console.WriteLine("try test");//여기까지 실행 안된다.
}
catch(System.DivideByZeroException)
{
Console.WriteLine("catch");//"catch"가 콘솔창에 뜬다.
}
catch(System.NullReferenceException)
{...}
위와 같이 catch의 구문에 예외 타입을 지정하여 원하는 예외만 잡을 수 도 있다.
예외를 처리하는 것 뿐만 아니라 임의로 발생시키는 것 또한 가능하다. 이를 위해 C#은 throw예약어를 제공한다.
사용법은 예외 타입의 인스턴스를 생성한 후 그것을 throw에 전달하면 끝이다.
string txt = Console.ReadLine();
if(txt != "123")
{
ApplicationException ex = new ApplicationException("암호가 틀립니다.");
throw ex;//예외 발생
}
Console.WriteLine("올바른 암호");
참고로 catch블록에서 throw를 쓰는 경우 예외 객체 없이 단독으로 사용 가능하며 예외 객체포함해서 쓰는 것보다 조금 더 예외발생 시 많은 정보를 준다.
try{...//error발생}
catch(System.Exception ex)
{
throw // or throw ex
}
위처럼 예외 객체를 전달안 할 때 throw시 예외에 호출 스택이 끝까지 남는다.
만약 위에서 부터 차례대로 하나라도 원하는 값이 나오지 않을경우 예외처리를 해야한다고 가정하면 아래와 같이 짤 수 있다.
if(LogText(aText) == false){
return;
}
if(LogText(bText) == false){
return;
}
...
이것을 try catch를 이용하면 좀 더 깔끔하게 만들 수 있다.
try
{
LogTextWithException(aText);//예외 발생시 아래 코드 실행 없이 바로 실행
LogTextWithException(bText);//예외 발생시 아래 코드 실행 없이 바로 실행
...
}
catch{...//예외 처리}
이렇게 예외를 사용해서 많은 에러를 효율적으로 탐지하고 실수를 예방할 수 있지만 try/catch를 남용해서는 안된다.
만약 예외를 사용하라는 규칙만 있다면 예외 발생을 남용하여 습관적으로 try/catch를 만들게 되고 이는 "예외를 먹는(swallowing exceptions)" 상황을 유발한다. 이는 실제로 프로그램에 문제가 있지만 외부에 아무런 문제 현상이 나타나지 않는 것을 뜻한다.
try
{
LogTextWithException(aText);//예외 발생시 아래 코드 실행 없이 바로 실행
LogTextWithException(bText);//예외 발생시 아래 코드 실행 없이 바로 실행
...
}
catch
{
//Empty catch문, 이럴 경우 에러가 발생해도 아무 조치도 안하게 되어 문제가 발생한다.
}
"따라서,
try/catch는 스레드 단위마다 단 한 번만 전역적으로 사용하자."또한 예외 처리는 예외 발생한 경우 처리가 매우 무겁기 때문에 조심히 사용하자.
예외 처리가 무겁기 때문에 마이크로소프트는 Parse 메서드를 out인자를 사용해 개선한 TryParse등을 BCL에 포함시키기도 하였다. 이 메소드는 문자열이 숫자로 바꿀 수 있는 경우에만 out 형식의 인자에 숫자값을 담고, 메서드 실행이 성공했는지 여부만 반환할 뿐 예외는 발생시키지 않아 빠르다.
- 적어도 공용(public) 메서드에 한해서는 인자값이 올바른지 확인하고, 올바른 인자가 아니라면 예외를 발생시킨다.
- 예외를 범용적으로 catch하는 것은 스레드마다 하나만 둔다. 그 외에는 catch구문에 반드시 예외 타입을 적용한다.
- try/finally 조합은 언제든 사용할 수 있다.
- 성능상 문제가 발생할 수 있는 경우, 즉 호출 시 예외가 대략으로 발생하는 메서드가 있다면 예외 처리가 없는 메서드를 함께 제공한다.