본문 바로가기
전공/C# 프로그래밍

15강. 예외 처리

by 임 낭 만 2023. 6. 14.

기본/고급 예외 처리

예외 (Exception)

  • 개발자가 생각하는 시나리오에서 벗어나는 사건을 예외라고 함
    • 예를 들어, 배열의 범위 밖의 배열의 요소를 접근하려고 시도
  • 예제코드 for 문에서 배열 범위 밖의 요소 접근 시, 예외 메시지 출력하고 프로그램 종료
    • 배열 객체는 예외에 대한 상세정보를 IndexOutofRangeException 객체에 담은 후 Main() 메소드에 던짐
    • Main() 메소드는 예외를 CLR에 던짐
    • CLR까지 전달된 예외는 “처리되지 않은 예외”가 되고, 예외 관련 메시지 출력 후 강제 종료
using System;

namespace Exception
{
    class MainApp
    {
        static void Main(string[] args)
        {
            int[] arr = { 1, 2, 3 };
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(arr[i]);
            }
            Console.WriteLine("종료"); // 실행 X
        }
    }
}

예외 처리 (Exception Handling)

  • 예외가 프로그램의 오류 또는 다운으로 이어지지 않도록 적절하게 처리
    • 기본 에러 처리와 고급 에러 처리로 나눌 수 있음
  • 기본 예외 처리
    • 예외가 발생하지 않게 사전에 해결
using System;

namespace Exception
{
    class MainApp
    {
        static void Main(string[] args)
        {
            int[] arr = { 1, 2, 3 };
            for (int i = 0; i < 5; i++)
            {
                // 기본 에러 처리
                if (i < arr.Length) // 인덱스가 배열의 길이를 넘는지 사전에 확인
                    Console.WriteLine(arr[i]);
                else
                    Console.WriteLine("인덱스 범위를 넘었습니다.");
            }
            Console.WriteLine("종료");
        }
    }
}

고급 에러 처리 : try ~ catch로 예외 받기

  • C#에서 예외를 받을 때 try ~ catch 문을 이용
    • 이전 슬라이드 예제에서 배열이 IndexOutRangeException 예외를 던졌을 때, Main() 메소드는 try ~ catch 문으로 예외를 받을 수 있음
  • 실행코드를 try 블록에 작성하고, try 블록에서 던지는 예외는 catch 블록에서 받음
    • catch 문은 try 블록에서 던지는 예외 객체와 형식이 일치해야 받을 수 있음
    • 모든 catch 문에서 예외를 받지 못하면 “처리되지 않은 예외”로 남게 됨

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(arr[i]);
                }
                Console.WriteLine("정상적 수행완료");
            }
            catch( IndexOutOfRangeException e )	// catch(IndexOutOfRangeException)와 같이 예외 타입만 작성도 가능
            {
                Console.WriteLine($"예외가 발생했습니다 : {e.Message}");
            }

            Console.WriteLine("종료");
        }
    }
}


System.Exception 클래스

System.Exception 클래스

  • C#에서 모든 예외 클래스는 System.Exception 클래스를 상속 받음
    • IndexOutRangeException 예외도 System.Exception으로부터 파생
    • 관례상으로는 응용 프로그램 개발자 정의하는 예외는 ApplicationException 타입을 상속하고, CLR에서 미리 정의된 예외는 SystemException 타입을 상속
    • 하지만, 최근 닷넷 가이드라인 문서는 응용프로그램 개발자가 만드는 예외를 System.Exception에서 직접 상속 받도록 권장

  • 상속관계로 인해 모든 예외 클래스는 System.Exception 형식으로 변환 가능
    • System.Exception 형식의 예외를 받는 catch절 하나면 모든 예외를 받을 수 있
  • 하나의 System.Exception 타입 사용 시, 코드를 면밀히 검토 필요
    • 처리하지 않아야 할 예외까지 처리하는 일이 없도록 주의

System.Exception 클래스의 주요 멤버

using System;

namespace ExceptionClass
{
    class MainApp
    {
        static void Main(string[] args)
        {
            int[] arr = { 1, 2, 3 };
            try
            {
                for (int i = 0; i < 5; i++)
                {
                    Console.WriteLine(arr[i]);
                }
                Console.WriteLine("정상적 수행완료");
            }
            catch (IndexOutOfRangeException e)
            {
                Console.WriteLine($"예외 Message : {e.Message}");
                Console.WriteLine($"예외 Source : {e.Source}");
                Console.WriteLine($"예외 StackTrace : {e.StackTrace}");
                Console.WriteLine($"예외 ToString : {e.ToString()}");
            }
            Console.WriteLine("종료");
        }
    }
}


예외 던지기

예외 처리기 : throw 문

  • 예외 객체는 throw 문을 통해 던지고,
    • 던져진 예외는 try~catch 문을 통해 받을 수 있음

예외 처리기 : throw 식

  • C# 7.0부터는 throw를 식으로도 사용할 수 있도록 지원

using System;

namespace Throw
{
    class MainApp
    {
        static void Main(string[] args)
        {
            try
            {
                int? a = null;
                int b = a ?? throw new ArgumentNullException();
            }
            catch (ArgumentNullException e)
            {
                Console.WriteLine(e.Message);
            }
            try
            {
                int[] array = new[] { 1, 2, 3 };
                int index = 4;
                int value = array[
                index >= 0 && index < 3
                ? index : throw new IndexOutOfRangeException()
                ];
            }
            catch (IndexOutOfRangeException e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
}


finally 문

고급 에러 처리 : try ~ catch와 finally

  • try 블록 코드 실행 중 에러가 던져지면 catch 절로 바로 뛰어넘어 옴
    • 만약 try 블록의 자원 해제 같은 중요한 코드를 미처 실행하지 못한다면 문제가 됨
  • try ~ catch문의 마지막에 연결해서 사용하는 finally 절
    • try 절이 실행된다면 finally 절은 어떤 경우라도 실행 됨
    • 예외 처리 시, 자원 해제하는 코드를 넣어두는 용도로 적합

  • try 절 안에 return문이나 throw문이 사용되어도 finally 절은 반드시 실행
    • 단, finally 절 안에 return문 사용은 컴파일 에러 발생

using System;

namespace Finally
{
    class MainApp
    {
        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("Divide()  끝");
            }
        }
        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 e)
            {
                Console.WriteLine("에러 : " + e.Message);
            }
            catch (DivideByZeroException e)
            {
                Console.WriteLine("에러 : " + e.Message);
            }
            finally
            {
                Console.WriteLine("프로그램을 종료합니다.");
            }
        }
    }
}


사용자 정의 예외 클래스

  • 개발자는 C#에서 정의 되어 있지 않은 새로운 예외 클래스를 만들 수 있음
    • 반드시 Exception 클래스를 상속하는 파생 클래스

  • 사용자 정의 예외 예제 프로그램 (Next 슬라이드에 코드 설명)
    • 8비트 정수 (색을 구성하는 Alpha, Red, Green, Blue 값)를 매개변수로 입력 받아 32비트 정수 안에 병합하는 MergeARGB() 메소드 구현
    • 매개변수 입력 값이 0~255 사이이고, 이 범위를 벗어나면 InvalidArgumentException 예외 발생
using System;

namespace MyException
{
    class InvalidArgumentException : Exception
    {
        public InvalidArgumentException() { }
        public object Argument {  get;  set;  }
        public string Range {  get;  set;  }
    }
    class MainApp
    {
        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 & 0xFF000000) |
                   (red   << 16 & 0x00FF0000) |
                   (green << 8  & 0x0000FF00) |
                   (blue        & 0x000000FF);
        }
        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 e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine($"Argument:{e.Argument}, Range:{e.Range}");
            }          
        }
    }
}


예외 필터

예외 필터 (Exception Filter)

  • C# 6.0부터는 catch 절이 특정 조건을 만족하는 예외 객체를 받도록 지원
    • when 키워드를 이용해서 제약 조건을 기술

using System;

namespace ExceptionFiltering
{
    class FilterableException : Exception
    {
        public int ErrorNo {get;set;}
    }
    class MainApp
    {
        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 };
                else
                    Console.WriteLine($"Output : {num}");
            }
            catch (FilterableException e) when (e.ErrorNo < 0)
            {
                Console.WriteLine("Negative input is not allowed.");
            }
            catch(FilterableException e) when (e.ErrorNo > 10)
            {
                Console.WriteLine("Too big number is not allowed.");
            }
        }
    }
}


예외 처리의 사용

  • try ~ catch 문을 이용한 예외 처리 효과
    • 예외를 종류별로 정리하여 코드의 가독성이 향상
    • try 문에서 발생할 수 있는 다수의 예외 상황을 하나의 catch 문에서 처리 가
    • StackTrace 프로퍼티를 통해 예외 발생 위치추적 용이
  • try ~ catch 문을 통한 예외 처리의 단점
    • 기본 예외처리보다 예외 발생 시 처리 시간이 오래 걸림

댓글