프로그래밍/C#

[C#] 예외를 처리하는 방법 (catch, Exception, rethrow)

갓똥 2020. 4. 20. 18:14
728x90
반응형

1. try ~ catch

using System;
using System.Net;

class Program {
    static void Main() {
        WebClient wc = new Webclient();
        
        string s = wc.DownloadString("http://www.naver.com");
        
        Console.WriteLine(s);
    }
}

 

웹클라이언트 객체를 만들어 주소의 소스를 받는 코드이다.
위 코드는 문제없이 네이버의 코드를 불러오는데
실수로 주소를 다르게 입력하거나 입력하지 않는다면 예외가 일어난다.
이러한 예외 처리는 어떻게 해야할까?
문제가 일어나는 부분의 메소드를 우클릭 후 정의로 이동해보면 알 수 있다.

정의로 이동 후 왼쪽 +버튼을 눌러 펼처보면 친절하게 예외에 대한 설명이 있다.

현재 3개의 예외가 일어날 수 있고 위의 경우 2번째 주소가 올바르지 못한 예외였다.
이제 예외에 대한 처리를 해보자.

 

using System;
using System.Net;

class Program {
    static void Main() {
        try {
            WebClient wc = new Webclient();
            string s = wc.DownloadString("http://www.naver.com");
            Console.WriteLine(s);
        }
        catch(System.ArgumentNullException e) {
            Console.WriteLine("매개 변수가 null");
        }
        catch(System.Net.WebException e) {
            Console.WriteLine("잘못된 주소");
        }
        catch(System.NotSupportedException e) {
            Console.WriteLine("스레드간 동시 호출");
        }
    }
}

 

위와 같이 예외를 처리할 수 있다.
모든 예외는 Exception클래스로부터 파생되므로 Exception클래스로 하나로 처리도 가능하다.
또한, 원하는 예외는 처리 후 나머지 예외에 대하여는 동일하게 처리도 가능하다.
주의할점은 switch문 같이 예외가 나왔을 때 그에 맞는 예외를 찾아가는게 아닌
무조건 위에 있는 catch문부터 찾아가기 때문에 원하는 예외가 있을 경우 위에 적어야 한다.

 

 ① 모든 예외 한번에 처리

using System;
using System.Net;

class Program {
    static void Main() {
        try {
            WebClient wc = new Webclient();
            string s = wc.DownloadString("http://www.naver.com");
            Console.WriteLine(s);
        }
        catch(System.Exception e) {
            Console.WriteLine("한번에 처리");
        }
    }
}

 ② 원하는 예외는 따로 처리

using System;
using System.Net;

class Program {
    static void Main() {
        try {
            WebClient wc = new Webclient();
            string s = wc.DownloadString("http://www.naver.com");
            Console.WriteLine(s);
        }
        catch(System.Net.WebException e) {
            Console.WriteLine("잘못된 주소일경우에 대한 처리");
        }
        catch(System.Exception e) {
            Console.WriteLine("나머지 예외에 대한 처리");
        }
    }
}

 ③ 잘못된 방법

using System;
using System.Net;

class Program {
    static void Main() {
        try {
            WebClient wc = new Webclient();
            string s = wc.DownloadString("http://www.naver.com");
            Console.WriteLine(s);
        }
        catch(System.Exception e) {
            Console.WriteLine("모든 예외에 대한 처리");
        }
        catch(System.Net.WebException e) {
            Console.WriteLine("주소가 잘 못 되어도 이 예외 처리로 오지 않음");
        }
    }
}

 


2. System.Exception

using System;

class Program {
    public static void Goo() {
        int[] arr = {1, 2, 3};
        arr[3] = 10;
    }
    public static void Foo() {
        Goo();
    }
    static void Main() {
        Foo();
    }
}

 

위는 예외를 발생시키기 위해 일부러 맞지 않게 작성한 코드이다.
보면 arr[3]은 없기때문에 저부분에서 인덱스를 벗어난 에러를 뱉을 걸 알 수 있다.
이제 예외 처리를 해야할텐데 어디서 해야 할까?
예외 처리는 문제가 일어난 곳에서 시작해서 예외에 대한 처리가 없다면
해당 함수를 호출한 곳으로 넘어간다.
따라서 Goo() -> Foo() -> Main()으로 넘어가기에 Main에서 예외처리를 해도 무방하다.

 

using System;

class Program {
    public static void Goo() {
        int[] arr = {1, 2, 3};
        arr[3] = 10;
    }
    public static void Foo() {
        Goo();
    }
    static void Main() {
        try {
            Foo();
        }
        catch(IndexOutOfRangeException e) {
            Console.WriteLine(e.Message);
            Console.WriteLine(e.StackTrace);
        }
    }
}

 

이 예외 또한 Exception 클래스로부터 파생되기에 Exception으로 받아도 무방하다.
또, Exception클래스에서 중요한 멤버가 2가지 있다.
하나는 Message로 예외가 일어난 이유에 대해 설명해준다.
또 하나는 StackTrace로 어디서 예외가 발생했는지에 대해 알려준다.
둘 다 default로 예외처리를 하지 않았을 때 뜨지만, 디버깅 시 중요하므로 알고 있으면 편하다.

 ① 모든 예외 클래스는 System.Exception 클래스로부터 파생된다.

 

 ② System.Exception의 주요 멤버

 


3. 예외를 다시 전달하기 (retrhow)

using System;

class Server {
    public void Connect() {
        throw new TimeoutException();
    }
}

class Program {
    static void Foo() {
        Server wc = new Server();
        try {
            wc.Connect();
        }
        catch(TimeoutException e) {
            // ...
        }
    }
    static void Main() {
        try {
            Foo();
        }
        catch(Exception e) {
            Console.WriteLine(e.StackTrace);
        }
    }
}

 

위와 같은 코드가 있다.
Server라는 클래스가 있고 Connect라는 메소드를 통해 접속한다고 해보자.
접속을 시도할 때 일정시간 접속이 안된다면 예외를 발생한다고 하자. (테스트를 위해 항상 예외가 발생하게 처리했음)
그리고 Foo함수에서 서버에 접속을 하려 할텐데 현재 항상 예외가 발생하게 해놓았으므로
무조건 예외가 일어날 것이다.
그리고 Foo함수에서 접속 실패에 대한 예외를 처리하는데
Main에서도 예외가 일어났는지에 대해 알고 싶다고 해보자.
그럼 Foo함수에서 처리하고 그 예외를 다시 Main으로 보내야 할 텐데 어떻게 해야할까?
이 때 throw키워드로 보낼 수 있다.

 

using System;

class Server {
    public void Connect() {
        throw new TimeoutException();
    }
}

class Program {
    static void Foo() {
        Server wc = new Server();
        try {
            wc.Connect();
        }
        catch(TimeoutException e) {
            // ...
            throw e; // 1번
            throw;   // 2번 - 좋은 표기법
        }
    }
    static void Main() {
        try {
            Foo();
        }
        catch(Exception e) {
            Console.WriteLine(e.StackTrace);
        }
    }
}

 

17~18번 라인과 같이 예외를 보낼 수 있다.
두 가지 경우에 대해 적어놨는데 2번째 방법이 좋은 표기법이다.
이유는 Main에서 StackTrace로 예외의 근원지를 찾으려 할 때
throw e로 처리하면 17번 라인을 예외의 근원지로 본다.
사실 예외가 발생한건 5번 라인때문인데 17번으로 보기에 디버깅시 좋지 않다.
하지만 18번 라인과 같이 throw로만 처리하면 예외의 근원지를 5번라인으로 보며 Main으로 예외도 보내준다.

 ① 발생된 예외를 다시 전달 하는 방법

throw로 던지는게 좋다.

728x90
반응형