Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

nomad-programmer

[Programming/C#] 예외 처리 본문

Programming/C#

[Programming/C#] 예외 처리

scii 2020. 9. 21. 19:14

예외는 throw 문을 이용하여 던진다. 그래서 던진 예외를 try~catch로 받는다.

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

int? foo = null;
// foo는 null이므로 bar에 foo를 할당하지 않고 throw 식이 실행된다.
int bar = foo ?? throw new ArgumentNullException();


// 조건 연산자 안에서도 사용할 수 있다.
int[] array = new int[] {1, 2, 3};
int index = 4;
// 삼항 연산자를 이용한 예외 던지기
int value = array[
    index >= 0 && index < 3 ? index : throw new IndexOutOfRangeException()
];

예외 처리 및 발생에 대한 예제

using System;

namespace test
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                int? a = null;
                int b = a ?? throw new ArgumentNullException();
            }
            catch (ArgumentNullException err)
            {
                Console.WriteLine(err);
            }

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


/* 결과

System.ArgumentNullException: Value cannot be null.
  at test.Program.Main (System.String[] args) [0x00015] in <ee8587fc5c3644dfa10a5df84cb7d972>:0 
System.IndexOutOfRangeException: Index was outside the bounds of the array.
  at test.Program.Main (System.String[] args) [0x0004a] in <ee8587fc5c3644dfa10a5df84cb7d972>:0 

*/

try~catch와 finally

try블록에서 코드를 실행하거나 예외가 던져지면 프로그램의 실행이 catch 절로 바로 뛰어 넘어온다. 만약 예외 때문에 try 블록의 자원 해제 같은 중요한 코드를 미처 실행하지 못한다면 이는 곧 버그를 만드는 원인이 된다. 예를 들어 데이터베이스의 커넥션을 닫는 코드가 있는데 갑자기 발생한 예외 때문에 이것을 실행하지 못하면 사용할 수 있는 커넥션이 점점 줄어 나중에는 데이터베이스에 전혀 연결할 수 없는 상태에 이를 수 있다.
finally 절은 try~catch 문의 제일 마지막에 연결해서 사용하는데 이곳에 다음과 같이 뒷정리 코드를 넣어두면 된다.

try {
    dbconn.Open()
}
catch (XXXException err) {
}
catch (YYYException err) {
}
finally {
    dbconn.Close()
}

자신이 소속되어 있는 try절이 실행된다면 finally 절은 어떤 경우라도 실행된다.

try 절 안에서 return 문이나 throw 문이 사용되더라도 finally 절은 반드시 실행된다.

finally 를 사용한 예제

using System;

namespace test
{
    internal class Program
    {
        static int Divide(int divisor, int dividend)
        {
            try
            {
                Console.WriteLine("Divide() 시작");
                return divisor / dividend;
            }
            catch (DivideByZeroException err)
            {
                Console.WriteLine("Divide() 예외 발생");
                throw;
            }
            finally
            {
                Console.WriteLine("Divide() 끝");
            }
        }

        public static void Main(string[] args)
        {
            try
            {
                Console.Write("제수 입력: ");
                string temp = Console.ReadLine();
                int divisor = Convert.ToInt32(temp);

                Console.Write("피제수 입력 : ");
                temp = Console.ReadLine();
                int dividend = Convert.ToInt32(temp);

                Console.WriteLine(
                    "{0} / {1} = {2}", divisor, dividend, Divide(divisor, dividend));
            }
            catch (FormatException err)
            {
                Console.WriteLine("에러 : " + err.Message);
            }
            catch (DivideByZeroException err)
            {
                Console.WriteLine("에러 : " + err.Message);
            }
            finally
            {
                Console.WriteLine("프로그램 종료");
            }
        }
    }
}


/* 결과

제수 입력: 7
피제수 입력 : 0
Divide() 시작
Divide() 예외 발생
Divide() 끝
에러 : Attempted to divide by zero.
프로그램 종료

*/

finally 안에서 예외가 또 발생하면?

finally 블록에서 예외가 발생하면 받아주거나 처리해주는 코드가 없으므로 이 예외는 "처리되지 않은 예외"가 된다. 만약 현재 수준의 코드에서 예외가 일어날 가능성을 완전히 배제할 수 없다면 이 안에서 다시 한번 try~catch 블록을 사용해야 한다.


사용자 정의 예외 클래스 생성

C#에서 사용하는 모든 예외 객체는 System.Exception 클래스로부터 파생되어야 한다. 이 규칙에 의거하여 Exception 클래스를 상속하기만 하면 새로운 예외 클래스를 만들 수 있다.
사용자 정의 예외는 그렇게 자주 필요하진 않지만 특별한 데이터를 담아 예외 처리 루틴에 추가 정보를 제공하고 싶다거나 예외 상황을 자세히 설명하고 싶을 때에는 사용자 정의 예외 클래스가 필요하다.

사용자 정의 예외 예제

using System;

namespace test
{
    class InvalidArgumentException : Exception
    {
        public InvalidArgumentException()
        {
        }

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

        public object Argument { get; set; }

        public string Range { get; set; }
    }

    internal class Program
    {
        static uint MergeARGB(uint alpha, uint red, uint green, uint blue)
        {
            uint[] args = new uint[] {alpha, red, green, blue};
            foreach (uint arg in args)
            {
                if (arg > 255)
                {
                    throw new InvalidArgumentException()
                    {
                        Argument = arg,
                        Range = "0~255"
                    };
                }
            }

            return (alpha << 24 & 0xff00_0000) | (red << 16 & 0x00ff_0000) |
                   (green << 8 & 0x0000_ff00) | (blue & 0x0000_00ff);
        }

        public static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("0x{0:X}", MergeARGB(255, 111, 111, 111));
                Console.WriteLine("0x{0:X}", MergeARGB(1, 65, 192, 128));
                Console.WriteLine("0x{0:X}", MergeARGB(0, 255, 255, 300));
            }
            catch (InvalidArgumentException err)
            {
                Console.WriteLine(err.Message);
                Console.WriteLine($"Argument : {err.Argument}, Range : {err.Range}");
            }
        }
    }
}


/* 결과

0xFF6F6F6F
0x141C080
Exception of type 'test.InvalidArgumentException' was thrown.
Argument : 300, Range : 0~255

*/

예외 필터 (Exception Filter)

C# 6.0 부터 catch 절이 받아들일 예외 객체에 제약 사항을 명시해서 해당 조건을 만족하는 예외 객체에 대해서만 예외 처리 코드를 실행할 수 있도록 "예외 필터 (Exception Filter)" 가 도입되었다.
예외 필터는 catch() 문 뒤에 when 키워드를 이용해 제약 조건을 기술하면 된다. (when을 if라고 생각하면 된다)

using System;

namespace test
{
    public class FilterableException : Exception
    {
        public int ErrorNo { get; set; }
    }

    internal class Program
    {
        public 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};
                }

                Console.WriteLine($"Output : {num}");
            }
            catch (FilterableException err) when (err.ErrorNo < 0)
            {
                Console.WriteLine("Negative input is not allowed");
            }
            catch (FilterableException err) when (err.ErrorNo > 10)
            {
                Console.WriteLine("Too big number is not allowed");
            }
        }
    }
}


/* 결과

Enter Number Between 0~10
2
Output : 2

Enter Number Between 0~10
-1
Negative input is not allowed

Enter Number Between 0~10
12
Too big number is not allowed

*/

정리

try~catch 문을 이용한 예외 처리는 실제 일을 하는 코드와 문제를 처리하는 코드를 깔끔하게 분리시킴으로써 코드를 간결하게 만들어 준다. 예외 처리의 장점은 이 뿐만이 아니다.
예외 객체의 "StackTrace 프로퍼티" 를 통해 문제가 발생한 부분의 소스 코드 위치를 알려주기 때문에 디버깅이 아주 용이하다.

using System;

namespace test
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                int a = 1;
                Console.WriteLine(3 / --a);
            }
            catch (DivideByZeroException err)
            {
                Console.WriteLine(err.StackTrace);
            }
        }
    }
}


/* 결과

  at test.Program.Main (System.String[] args) [0x00004] in <6d3f3d9a741c4fb8aa441a938e877378>:0 

*/

예외 처리는 문제점을 하나로 묶어 내거나 코드에서 발생할 수 있는 오류를 종류별로 정리해주는 효과가 있다. 예를 들어 try 블록 코드 중에서 DivideByZeroException 예외를 일으킬 수 있는 부분은 둘 이상일 수도 있지만, 이 형식의 예외를 받는 catch 블록 하나면 모두 처리할 수 있다. 
이렇게 예외 처리를 이용해서 오류를 처리하는 코드는 작성하기에도 쉽고, 나중에 다시 읽기에도 좋다.

Comments