상세 컨텐츠

본문 제목

[C#] 예외처리

.NET/C#

by 클리엘 클리엘 2021. 10. 15. 14:11

본문

728x90

예외란 컴파일 단계에서 파악할 수 없는 오류로 대부분 프로그램 동작중에 발생하며 다른 말로 '런타임 에러'라고 표현하기도 합니다.

class Program
{
    static void Main(string[] args)
    {
        string name = args[0];
        string greeting = args[1];

        WriteLine($"{name}님 {greeting}");
    }
}

예제에서는 args매개변수를 통해 2개의 문자열을 구분해 처리하고 있습니다. 프로그램을 보면 문자열 값이 무조건 2개는 들어온다는 가정하에 만들어진 것으로 소스상으로는 아무런 문제가 없지만 사용자가 프로그램 실행 시 문자열 값을 하나만 주는 경우라면 예외가 발생하게 될 것입니다.

프로그램은 처리할 수 없는 예외상황을 마주하게 되면 문제가 발생한 객체(예제에서는 배열)는 예외에 관한 정보를 예외처리 객체에 담아 코드를 실행한 메서드인 Main() 메서드에 던져주게 됩니다. 하지만 Main()메서드는 별도의 예외처리가 가능한 로직이 없으므로 이것을 다시 CLR에 전달하게 되고 CLR은 예외처리 객체에 담긴 정보를 사용자에게 표시한 후 프로그램 실행을 중지하게 됩니다.

 

예외로 인해 프로그램 실행이 중지되는것을 방지하려면 Main() 메서드에 다음과 같이 예외가 발생할 경우 처리할 수 있는 방안을 마련해 주면 됩니다.

class Program
{
    static void Main(string[] args)
    {
        try {
            string name = args[0];
            string greeting = args[1];

            WriteLine($"{name}님 {greeting}");
        }
        catch (Exception ex)
        {
            WriteLine(ex.Message);
        }

        WriteLine("완료");
    }
}

이때 사용되는 것이 try ~ catch 문인데 try 내부 블록에 있는 코드들이 실행되다가 예외가 발생하면 catch블록 안으로 실행이 바뀌게 됩니다.

따라서 프로그램을 실행한 뒤 매개변수값이 하나만 주어지는 경우에도 catch 쪽으로 실행을 옮겨 사용자에게 오류 메시지를 표시하고 이후에 프로그램에 남은 나머지 부분도 모두 실행 후 프로그램이 정상 종료되는 것을 확인할 수 있습니다.

 

try ~ catch 에서 catch는 예외가 발생한 경우 처리되는 부분입니다. 이때 Exception 형식의 예외가 지정된 것을 볼 수 있는데 Exception은 데이터 형식에서 Object와 같은 것으로 모든 예외 객체는 이 Exception으로부터 상속받아 만들어집니다. 따라서 Exception은 사실상 모든 예외를 받아들일 수 있는 예외가 됩니다.

 

만약 모든 예외가 아닌 특정 예외를 구분하고자 한다면 아래와 같이 해당 예외객체를 그대로 지정해 주기만 하면 됩니다.

class Program
{
    static void Main(string[] args)
    {
        try {
            string name = args[0];
            string greeting = args[1];

            WriteLine($"{name}님 {greeting}");
        }
        catch (IndexOutOfRangeException ex)
        {
            WriteLine("배열범위 오루" + ex.Message);
        }
        catch (Exception ex)
        {
            WriteLine(ex.Message);
        }

        WriteLine("완료");
    }
}

보시는 바와 같이 catch는 예외의 객체를 구분할때 여러 개 사용될 수 있으며 예제에서 사용된 IndexOutRangeException객체는 배열 범위를 벗어난 처리가 시도되는 경우 발생하는 예외입니다. 특정 예외상황에서 별도의 처리가 필요한 경우 위에서 처럼 catch로 예외의 타입을 구분하여 처리합니다. 그러나 이런 경우에도 발생한 예외가 상위 어떤 예외 타입에도 일치하지 않는 경우가 생길 수 있고 이 때문에 마지막에는 Exception을 넣어주는 것이 좋습니다.

 

예외는 전혀 예상하지 못하는 경우에 발생하지만 필요에 따라서는 예외를 고의적으로 발생시킬 수도 있습니다.

class Program
{
    static void Main(string[] args)
    {
        try {
            if (args.Length <= 1) {
                throw new Exception("배열 범위를 벗어남");
            }

            string name = args[0];
            string greeting = args[1];

            WriteLine($"{name}님 {greeting}");
        }
        catch (Exception ex)
        {
            WriteLine(ex.Message);
        }

        WriteLine("완료");
    }
}

Main() 메서드에서는 args의 매개변수길이를 확인하고 만약 if 조건과 일치하지 않는다면 throw문을 통해 Exception예외 객체를 만들어 예외를 발생시키고 있습니다. 예외 타입은 Exception뿐만 아니라 다른 타입의 객체를 생성해 던져주는 것도 가능하지만  해당 타입을 받아서 처리해 주는 catch가 존재하는 경우에만 정상적인 예외처리가 가능할 것입니다.

 

C#7.0부터는 throw문을 문이 아닌 식으로도 사용할 수 있으므로 필요에 따라서는 아래와 같은 형태의 처리도 가능합니다.

class Program
{
    static void Main(string[] args)
    {
        try {
            string name = args[0];
            string greeting = args.Length == 2 ? args[1] : throw new Exception("배열 범위를 벗어남");

            WriteLine($"{name}님 {greeting}");
        }
        catch (Exception ex)
        {
            WriteLine(ex.Message);
        }

        WriteLine("완료");
    }
}

예외가 발생하면 발생한 예외의 객체 타입에 따라 지정된 catch 블록으로 처리가 이동하게 되는데 그 이후 공통적인 후속조치가 필요하다면 finally를 사용해야 합니다.

class Program
{
    static void Main(string[] args)
    {
        try {
            string name = args[0];
            string greeting = args.Length == 2 ? args[1] : throw new Exception("배열 범위를 벗어남");

            WriteLine($"{name}님 {greeting}");
        }
        catch (Exception ex)
        {
            WriteLine(ex.Message);
        }
        finally {
            WriteLine("예외 처리가 완료됨");    
        }

        WriteLine("완료");
    }
}

finally는 try 블록이 실행되면 무조건 실행되는 영역으로서 예외가 발생하든 안 하든 상관없이 실행됩니다. 심지어는 return문으로 실행을 중지시키는 경우가 발생하더라도 finally가 실행됩니다. 무조건 적인 실행이 보장되므로 반드시 실행되어야 하는 코드들은 finally안에 모아두면 됩니다.

 

경우에 따라 finally안에 또 다른 try ~ catch구문을 넣어두는 경우가 있습니다. finally안에서 또 다른 예외가 발생하는 경우를 대비하기 위해서인데 이러한 방법이 불가능하지는 않지만 try ~ catch 자체가 실행을 분기시키는 동작을 수행하므로 성능면에 있어서는 그리 도움되지 않습니다. 그러므로 try ~ catch를 남발하지 않도록 하는 것이 좋습니다.

 

catch는 지정된 예외형식이 일치해야만 해당 블록의 코드를 실행합니다. 이미 언급한 부분이지만 이 예외 형식은 Exception에서 파생된 객체입니다. 이 말을 뒤집어 보면 Exception 클래스에서 파생하는 클래스를 만들면

class MyException : Exception
{
    public MyException()
    {

    }

    public MyException(string Message) : base(Message)
    {
    }
}

내가 원하는 예외형식을 만들어 내는 것도 가능하다는 의미가 됩니다. 사용자 정의 예외를 만드는 건 Exception클래스로부터 상속만 받으면 기본적인 예외처리가 완성됩니다.

string greeting = args.Length == 2 ? args[1] : throw new MyException("배열 범위를 벗어남");

참고로 예외처리에서도 특정 조건을 설정할 수 있습니다. 만약 아래와 같이 예외 클래스가 만들어 졌을때

class MyException : Exception
{
    public MyException()
    {

    }

    public MyException(string Message) : base(Message)
    {
    }

    public int ArgsCount
    {
        get;set;
    }
}

MyException예외가 발생한 경우 ArgsCount값이 1인 경우에만 처리되어야 한다면 try ~ catch에서 when 절을 사용해 해당 조건의 필터를 적용시켜 줍니다.

class Program
{
    static void Main(string[] args)
    {
        try {
            string name = args[0];
            string greeting = args.Length == 2 ? args[1] : throw new MyException("배열 범위를 벗어남") { ArgsCount = args.Length };

            WriteLine($"{name}님 {greeting}");
        }
        catch (MyException ex) when (ex.ArgsCount == 1)
        {
            WriteLine(ex.Message);
        }
        finally {
            WriteLine("예외 처리가 완료됨");    
        }

        WriteLine("완료");
    }
}

 

728x90

'.NET > C#' 카테고리의 다른 글

[C#] 람다식  (0) 2021.10.19
[C#] 대리자와 이벤트  (0) 2021.10.18
[C#] 예외처리  (0) 2021.10.15
[C#] 일반화 프로그래밍  (0) 2021.10.14
[C#] 배열, 컬렉션, 인덱서  (0) 2021.10.13
[C#] 프로퍼티(Property)  (0) 2021.10.07

태그

관련글 더보기

댓글 영역