본문 바로가기

C#/이것이 C#이다

[ 이것이 C#이다 ] Chapter 12 . 예외처리

[ 학습 흐름 ]
  1. 예외에 대하여
  2. try ~ catch로 예외 받기
  3. System.Exception 클래스
  4. 예외 던지기
  5. try ~ catch 와 finally
  6. 사용자 정의 예외 클래스 만들기
  7. 예외 필터하기
  8. 예외 처리 다시 생각해보기

[ 예외에 대하여 ]

[ 예외 ]

- 프로그래머가 생각한 시나리오에서 벗어나는 사건


[ 예외 처리 ]

- 예외가 프로그램의 오류나 다운으로 이어지지 않도록 적절하게 처리하는 것

using System;
using System.Collections;
using System.Collections.Generic;

namespace KillingProgram;

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("종료");
    }
}

 

- 길이가 3개인 배열에서 4번째 요소에 접근하는 순간 예외처리 코드가 없어 프로그램이 다운된다 .

- 잘못된 인덱스를 통해 배열의 요소에 접근하려 들면 배열 객체가 문제에 대한 정보를 IndexOutOfRangeException객체에 담은후 Main()메소드에 던지는데 , Main()메소드에는 해당 예외를 처리할 방도가 없어 CLR에 던진다 .

- CLR로 온 예외는 처리되지 않은 예외가 되고 CLR은 예외객체에 담긴 내용을 출력후 프로그램을 강제종료한다 .


[ try - catch로 예외 받기 ]

[ try - catch ]

- 위 프로그램에서 배열이 IndexOutOfRangeException 예외를 던졌는데 Main()메소드에서 처리하지 못하였다 .

- 이를 해결하기 위해 예외를 Main()메소드가 try - catch 문을 통해 받으면 된다 .

try
{
	//실행코드
}
catch(예외 객체 1)
{
	//예외 발생시 처리
}
catch(예외 객체 2)
{
	//예외 발생시 처리
}

- try 절의 코드블록에는 예외가 일어나지 않을 경우에 실행되어야 하는 코드들이 들어간다 .

- catch 절의 코드블록에는 예외 발생시 처리 코드가 들어간다 .

- 이때 , catch 절은 try블록에서 던질 예외 객체와 형식이 일치해야 한다 .

- 만약 예외를 아무도 받지 못한다면 처리되지 않은 예외로 남게 된다 .

using System;
using System.Collections;
using System.Collections.Generic;

namespace KillingProgram;

class MainApp
{   
    static void Main(string[] args)
    {
        int[] arr = { 1, 2, 3 };

        try
        {
            for(int i=0;i<5;i++)
            {
                Console.WriteLine(arr[i]);
            }
        }
        catch(IndexOutOfRangeException e)
        {
            Console.WriteLine($"예외가 발생했습니다 : {e.Message}");
        }
        Console.WriteLine("종료");
    }
}

 

- try -catch 문으로 예외를 안전하게 받아 처리하고 프로그램을 종료하도록 수정하였다 .


[ System.Exception 클래스 ]

[ System.Exception ]

- C#에서 모든 예외 클래스는 반드시 System.Exception 클래스를 상속받아야 한다 .

- System.Exception 형식의 예외를 받는 catch 절 하나면 모든 예외를 다 받아낼 수 있다 .

- 섬세한 예외처리가 필요한 코드에서는 Exception 클래스만으로 대응하기 어려워 상황에 따른 사용이 필요하다 .

- 코드를 면밀히 검토 , 처리해야 하지 않을 예외까지 처리하는 일이 없도록 해야 한다 .


[ 예외 던지기 ]

[ 예외 던지기 ]

try
{
	// ...
    throw new Exception("예외를 던집니다");
}
//throw 문을 통해 던져진 예외 객체는 catch 문을 통해 받는다 .
catch(Exception e)
{
	Console.WriteLine(e.Message);
}

- 예외는 throw문을 통해서 던진다 .

using System;
using System.Collections;
using System.Collections.Generic;

namespace Throw;

class MainApp
{   
    static void DoSomething(int arg)
    {
        if (arg < 10)
            Console.WriteLine($"arg : {arg}");
        else
            throw new Exception("arg가 10보다 큽니다");
    }
    static void Main(string[] args)
    {
        try
        {
            DoSomething(1);
            DoSomething(3);
            DoSomething(5);
            DoSomething(9);
            DoSomething(11);//예외 발생 . 아래줄은 실행 안됨
            DoSomething(13);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

 


[ 예외식 ]

- throw는 보통 문으로 사용하지만 식으로도 사용 할 수 있다 .

using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;

namespace ThrowException;

class MainApp
{   
    
    static void Main(string[] args)
    {
        try
        {
            int? a = null;
            int b = a ?? throw new ArgumentNullException();
        }
        catch(ArgumentNullException e) 
        {
            Console.WriteLine(e);
        }

        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);
        }
    }
}

 


[ try ~ catch 와 finally ]

[ finally ]

-  try 블록에서 발생한 예외로 뒷부분의 자원 해제같은 중요한 코드를 실행하지 못한다면 버그의 원인이 된다 .

- 예외처리를 할때 중요한 코드의 실행과 같은 뒷마무리를 위해 finally 절이 제공된다 .

- try 절이 실행된다면 finally 절은 어떤 경우라도 실행된다 .

- 심지어 try절안에 return/throw문과 같은 흐름제어를 외부 코드로 옮기는 문장이 있더라도 반드시 실행된다 .

- 만약 finally 안에서 또 예외가 발생하면 받아주는 코드가 없기에 처리되지 않은 예외가 된다 

- finally 안에서 다시 try - catch를 쓰는 것도 방법 .

using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;

namespace Finally;

class MainApp
{   
    static int Divide(int divided, int divisor)
    {
        try
        {
            Console.WriteLine("Divide() 시작");
            return divided / divisor;
        }
        catch(DivideByZeroException e)
        { 
            Console.WriteLine("Divided() 예외 발생");
            throw e;
        }
        finally
        {
            Console.WriteLine("Divided() 끝");
        }
    }
    
    static void Main(string[] args)
    {
        try
        {
            Console.WriteLine("피제수를 입력하세요");
            string temp = Console.ReadLine();
            int divided = Convert.ToInt32(temp);

            Console.WriteLine("제수를 입력하세요");
            temp = Console.ReadLine();
            int divisor = Convert.ToInt32(temp);

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

 


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

[ 사용자 정의 예외 클래스 ]

- Exception 클래스를 상속한다면 새로운 예외 클래스를 만들 수 있다 .

- 특별한 데이터를 담아 예외 처리 루틴에 필요한 추가 정보를 제공하고 싶을때 사용 할 수 있다 .

using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;

namespace Finally;

class InvalidArgumentException:Exception
{
    public InvalidArgumentException() { }
    public InvalidArgumentException(string message) : base(message) { }
    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}");
        }


    }
}

[ 예외 필터하기 ]

[ 예외 필터 ]

- 예외 필터는 catch 절이 받아들일 예외 객체에 제약 사항을 명시 , 해당 조건을 만족하는 예외 객체는 예외처리코드 실행

- when 키워드를 통해 제약 조건을 기술한다 .

using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;

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 ex) when (ex.ErrorNo<0)
        {
            Console.WriteLine("Negetive input is not allowed");
        }
        catch (FilterableException ex) when (ex.ErrorNo>10)
        {
            Console.WriteLine("Too big number is not allowed");
        }
    }
}

 


[ 예외처리 다시 생각해보기 ]

[ 예외 처리의 장점 ]

- try ~ catch 문을 이용한 예외처리는 실제 일을 하는 코드 / 문제 처리 코드를 분리시킴으로 코드를 간결하게 만든다 .

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

- 여러 문제점을 하나로 묶거나 코드에서 발생할 수 있는 오류를 종류별로 정리해준다 .