예외상황이 생겼을때, CLR은 스스로 에러메세지를 출력하고, 프로그램은 죽어버린다. 따라서 그 뒤에있는 코드들은 실행되지도않은채 프로그램이 종료되게된다. 이렇게 예외를 잘 처리하지못하고 죽어버리는 프로그램은 신뢰할 수 없다. 따라서 프로그래머는 예외가 자신이 작성한 코드 내에서 처리되도록 조치를 취해야한다.


try~ catch로 예외받기


프로그램에서 Main() 메소드에 예외를 던졌을때, Main()메소드가 예외를 받지 못해서 프로그램이 죽는것이기 때문에,  Main()메소드가  예외를 받으면 이 문제를 해결할 수 있다. C# 에서는 try ~ catch 구문으로 이 문제를 해결한다.


try

{

//실행할 코드

}

catch( 예외객체1)

{

//예외가 발생했을때의 처리

}

catch( 예외객체2)

{

//예외가 발생했을때의 처리

}


try절에는 예외가 일어나지 않았을경우 실행되어야 할 코드가, catch에는 예외가 발생했을때의 처리코드가 들어간다. 이때 catch 절은 try 블록에서 던질 예외 객체와 형식이 같아야 한다. 


ex)


using System


namespace TryCatch

{

class MainApp

{

static void Main(string[] args)

{

int[] arr = {1,2,3};


try

{

for(int i =0; i<5; i++)

{

Console.WriteLine("{0}",arr[i]);

}

catch( IndexOutOfRangeException e)

{

Console.WriteLine("예외가 발생했습니다 : {0}",e.Message);

 //예외시 프로그램은 IndexOutOfRangeException 자료형의 예외를 던지고 이는 객체 e 의 Message안에 예외 정보를 담아 저장된다,

}

Console.WriteLine("종료");

}

}

}


System.Exception 클래스


C# 에서 모든 예외 클래스는 반드시 System.Exception 클래스로부터 상속받아야한다. IndexOutOfRangeException 예외 또한 이 클래스로부터 파생된 것이다. 따라서 상속관계로 인해 모든 예외 클래스들은 System.Exception 형식으로 간주할 수 있고, System.Excecption 형식의 예외를 받는 catch 절 하나면 모든 예외를 다 받을 수 있다.


ex)


try

{

for(int i = 0; i<5; i++)

Console.WriteLine("{0}",int[i]);

}

catch( Exception e )

{

}


예외상황에 따라 섬세한 예외 처리가 필요한 경우 , Exception 클래스를 남용해선 안된다. Exception 형식은 프로그래머가 발생할 것으로 계산한 예외 말고도 다른 예외까지 받아낼 수 있다. 그 예외가 현재 코드가아닌 상위 코드에서 처리해야할 예외라면 , 이 코드는 버그를 만들고 있는 셈이 되므로, 남용해선안된다.


예외 던지기


예외를 던질때는 throw문을 이용해서 던진다


try 

{

// 

throw new Exception(" 예외를 던집니다");

}

catch(Exception e)

{

Console.WriteLine("e.Message);

}


ex)


static void DoSomething ( int arg)

{

if( arg<10)

Console.WriteLine("arg : {0}" , arg);

else

throw new Exception("arg가 10보다 큽니다"); // 예외를 던져도 예외를 받을 코드가 이 메소드 안에 없기 때문에, 예외는 DoSometiong 메소드의 호출자에게 던져진다.

}


static void Main()

{

try

{

Dosomething(14);

}

catch( Exception e)

{

Console.WriteLine("{0}",e.Message);

}

}



try~catch 와 finally


try  블록을 실행하다가 예외가 던져지면 프로그램이 catch 절로 바로 넘어가버린다. 그로인해 try 블록 안에있는 자원해제같은 중요한 코드를 미처 실행하지 못한다면 이는 곧 버그의 원인이 된다. 예를들어, 데이터베이스의 커넥션을 닫는 코드가 있는데 , 예외때문에 이 코드가 실행되지 못한다면, 사용할 수 있는 커넥션이 점점 줄어 나중에는 데이터베이스에 연결할 수 없는 상태가 된다. 이때 finally 절을 try ~cathc 문 가장 마지막에 연결하여 사용하면, 자신이 소속되어있는 try 절이 실행된다면 finally 절은 어떤경우라도 실행된다. 심지어 try 절안에 return 혹은 throw 문이 사용되더라도 finally 문은 꼭 실행된다.


try

{

dbconn.Open; // dbconn은 데이터베이스 커넥션

//

}

catch (XXXException e)

{

}

catch(YYYException e)

{

}

finally

{

dbconn.Close();

}


ex)


static int Divide(int divisor , int dividend)

{

try

{    

Console.WriteLine("Divide() 시작");

return divisor / dividend;

}

catch(DivideByZeroException e)

{

Console.WriteLine("Divide() 예외 발생");

throw e;

}

finally

{

Console.WriteLine("Divided() 끝");

}


}


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


모든 예외 클래스는 System.Exception 클래스로부터 파생되어야 한다. 이 규칙에 의거해서, Exception 클래스를 상속하기만 한다면, 새로운 예외클래스를 정의할 수 있다.


class MyException : Exception

{

//

}


.NET 프레임 워크에서 100여가지가 넘는 예외형식을 제공하기 때문에 사용자 정의 예외클래스는 자주 사용하지는 않지만, .NET 프레임 워크에서 지원하지 않는 예외형식이거나, 예외상황을 더 잘 설명하고싶을때 이용한다.


ex)


namespace MyException

{

class InvalidArgumentException : Exception

{

public InvaildArgumentException() // 매개변수 없는 생성자

{

}

public InvalidArgumentException(string message) : Base(message) // 매개변수가 있는생성자, 기반클래스 ( Exception ) 에 매개변수 전달

{

}

pubilc object Argument

{

get;

set;

} // 프로퍼티


}


}


예외처리 다시 생각해보기


try~ catch 문을 이용한 예외 처리는 실제 일을 하는 코드와 문제를 처리하는 코드를 깔끔하게 분리시킴으로서 코드를 간결하게 만들어준다. 

이 뿐만 아니라 예외객체의 StackTrace 프로퍼티를 통해 문제가 발생한 부분의 소스코드 위치를 알려주기때문에 디버깅이 아주 용이하다.


ex)


using System


namespace StackTrace

{

class MainApp

{

static void Main(string args[])

{

try

{

int a =1;

Console.WriteLine(3/-a);

}

catch (DivideByZeroException e)

{

Console.WriteLine(e.StackTrace); // 위치 : StackTrace.MainApp.Main(String[] args) 파일 : D:\stackTrace\MainApp.cs : 줄12 출력

}

}

}

}


또한, 예외처리는 여러 문제점을 하나로 묶어내거나 코드에서 발생할 수 있는 오류를 종류별로 정리해주는 효과가 있다. 예를들어 try 블록의 코드에서 DivideByZeroException예외를 일으킬 수 있는 부분은 둘 이상일 수 있지만 , 이 형식을 받는 Catch 블록 하나면 모두 처리가 가능하다.













+ Recent posts