Professional Documents
Culture Documents
Chap13-Exception Handling A Deeper Look
Chap13-Exception Handling A Deeper Look
2/94
Exception 클래스의 프러퍼티를 사용한다.
사용자 정의 예외 타입을 생성한다.
참조변수를 사용해 메소드를 호출하거나 프러퍼티에 접근하기 전에, 이
참조변수가 null 인지 여부를 판단하기 위해 널 조건부 연산자 (?.)를 사용한다.
특정 변수가 값 뿐만 아니라 null을 가질 수 있도록 널 허용nullable 값 타입을
사용한다.
예외 필터exception filter 를 사용하여 예외를 잡기 위한 조건을 지정한다.
3/94
13.1 서론
13.2 예제: 예외 처리 없이 0으로 나누기
13.2.1 0으로 나누기
13.2.2 분모에 숫자가 아닌 문자 또는 심볼 넣기
13.2.3 예외 처리 없이 앱 종료
13.3 예제: DivideByZeroException과 FormatException 처리하기
13.3.1 try 블록으로 코드 둘러싸기
13.3.2 예외 잡기
13.3.3 잡지 못한 예외
13.3.4 예외 처리의 종료 모델
13.3.5 예외 발생 시 제어권의 흐름
13.4 닷넷 예외 계층 구조
13.4.1 SystemException 클래스
13.4.2 throw 메소드가 던질 수 있는 예외들
4/94
13.5 finally 블록
13.5.1 리소스 해제 코드를 finally 블록으로 이동하기
13.5.2 finally 블록 시연하기
13.5.3 throw 명령문을 사용하여 예외 던지기
13.5.4 예외를 다시 던지기
13.5.5 finally 블록 이후 리턴하기
13.6 using 명령문
13.7 Exception 클래스의 프러퍼티
13.7.1 InnerException 프러퍼티
13.7.2 그 밖에 Exception 클래스의 프러퍼티들
13.7.3 Exception 클래스의 프러퍼티와 스택 언와인딩
13.7.4 InnerException이 포함된 Exception 객체 던지기
13.7.5 Exception 객체에 대한 정보 표시하기
13.8 사용자 정의 예외 클래스
5/94
13.9 널(null) 참조변수 확인하기
13.9.1 널 조건부 연산자 (? .)
13.9.2 is 연산자와 as 연산자 사용하기
13.9.3 널 허용 타입
13.9.4 널 병합 연산자 (??)
13.10 예외 필터와 when 절
6/94
예외(예외 상황)는 프로그램 실행 중에 문제가 있음을 의미한다.
예외 처리exception handling를 통해 문제가 마치 없었던 것 처럼 앱이 정상적으로
수행된다.
예외 처리를 통해 명확하고 견고하고 내결함성이 뛰어난 앱을 작성할 수 있다.
7/94
Fig.13.1에서는 사용자로부터 두 개의 정수를 입력 받은 다음, 첫 번째 정수를
분자로 두 번째 정수를 분모로 하여 나누기를 수행한다.
이 예제에서, 메소드는 문제가 있음을 감지하면 예외 던지기throwing를 수행된다.
8/94
9/94
10/94
11/94
이 예제에서 사용자의 잘못된 입력에 의해 예외가 발생하였다.
Debug > Start Without Debugging (Ctrl+F5) 로 앱을 실행한다.
실행 중에 예외가 발생하면 앱이 작동을 멈췄음을 나타내는 대화 상자가
나타난다.
이때, Cancel 또는 Close Program 을 클릭하면 앱은 종료된다.
발생한 예외를 설명하는 오류 메시지가 화면에 출력된다.
12/94
스택 트레이스stack trace는 예외 클래스 이름 그리고 발생된 문제의 원인을 오류
메시지로 보여준다. 또한 오류 메시지에는 각 메소드 별로 예외에 이르기
까지의 실행 경로가 포함된다.
스택 트레이스는 프로그램을 디버그 하는데 도움이 된다.
오류 메시지의 첫 번째 라인에서는 발생된 예외 클래스를 지정하였다.
0으로 정수를 나눌 때, CLR은 DivideByZeroException 객체를 던진다.
정수 계산에서는 0으로 나누기를 허용하지 않는다.
13/94
FormatException은 int.Parse가 정수가 아닌 문자열을 받았을 때 발생한다.
발생된 예외를 처리하지 못하면 앱은 그 자리에서 종료된다.
경우에 따라서, 예외가 발생하여 스택 트레이스에 표시되더라도 앱이 계속
실행되는 경우도 있다.
이 경우, 잘못된 결과가 나올 수 있다.
14/94
Fig.13.2에서는 실행 중 발생할 수 있는 두 종류의
예외(DivideByZeroException과 FormatException)를 잡아catch 처리handling
하는 방법을 보여준다.
오류 메시지 내용을 화면에 표시하여 사용자가 다른 값을 입력할 수 있도록
한다.
15/94
16/94
17/94
18/94
19/94
Int32.TryParse 메소드는 string 타입의 문자열을 int 값으로 변환한다. 항상
변환되는 것이 아니고 변환을 실패하는 경우도 있다.
이 메소드는 두 개의 인수가 필요하다. 첫 번째는 파싱parsing 할 문자열이고 두
번째는 변환된 값이 저장될 변수이다.
성공적으로 파싱되면 이 메소드는 bool 타입의 true를 리턴한다.
변환이 실패한 경우, 0 값이 두 번째 인수에 할당된다.
20/94
21/94
오류 방지 방법 13.1
22/94
try 블록에는 예외를 던질 수 있는 코드와 예외 발생 시 건너 뛰어야 하는 코드를
포함하고 있다.
23/94
try 블록에서 예외가 발생하면 예외 객체를 만들어, 이 객체에 대응되는 catch
블록으로 보내 처리한다.
try 블록 바로 다음에 하나 이상의 catch 블록이 선언되어 있어야 한다.
따라서, 하나의 try 블록과 다수의 catch 블록으로 한 쌍을 이룬다.
24/94
던져진 예외 객체의 클래스 타입과 catch 블록에 있는 매개변수의 클래스
타입이 서로 일치하지 않은 경우를 잡지 못한 예외uncaught exception 또는 처리되지
않은 예외unhandled exception 라고 한다.
Debug > Start Debugging (F5)를 통해 구동했을 때 런타임 환경이 잡지 못한
예외가 감지되면, 앱은 일시 중지되고 Exception Assistant (Fig.13.3) 화면이
나타난다.
25/94
Throw point Exception Assistant
26/94
예외가 발생하는 지점을 던지는 지점throw point이라고 한다.
try 블록에서 예외가 발생하면, 제어권은 던져진 예외 객체의 클래스 타입과
일치하는 첫 번째 catch 블록으로 즉시 이동한다.
예외가 처리 된 후, 제어권은 마지막 catch 블록 이후 부터 다시 시작된다.
이를 예외 처리의 종료 모델이라고 한다.
27/94
일반적인 프로그래밍 오류 13.1
28/94
C#에서는 Exception 클래스와 Exception 클래스의 파생 클래스들로 부터
생성된 예외 객체들을 던지고 잡을 수 있다.
다른 닷넷 언어(C/C++/JAVA/Phython)에서 던져진 예외 객체들도 모두 catch
블록을 통해 잡을 수 있다.
29/94
30/94
닷넷 예외 클래스 계층 구조에서 Exception 클래스는 기반 클래스이다.
CLR은 SystemException를 생성한다.
범위를 벗어난 배열 색인에 액세스 하려고 하면, CLR은
IndexOutOfRangeException 타입의 예외 객체를 던진다.
널(null) 값을 갖고 있는 참조변수 (즉, 어떤 객체도 참조하고 있지 않은
참조변수)를 사용하려고 하면 NullReferenceException이 발생한다.
31/94
catch 블록의 헤더에서 매개변수가 기반 클래스 타입으로 선언되어 있으면,
예외 클래스 계층 구조에서 이 기반 클래스 하위에 있는 모든 파생 클래스
타입의 예외를 catch 블록이 잡을 수 있다.
Exception 타입의 매개변수가 지정된 catch 블록은 모든 예외를 잡을 수 있다.
그러나 이러한 방법은 예외 상황이 매우 포괄적이기 때문에 예외를 처리하기
어려울 수 있다.
32/94
일반적인 프로그래밍 오류 13.2
33/94
Visual Studio 온라인 문서에서 int.Parse 메소드를 검색해 볼 것.
https://docs.microsoft.com/ko-kr/dotnet/api/system.int32.parse?view=netframework-
4.8#System_Int32_Parse_System_String_
온라인 문서의 Exceptions 부분을 보면, int.Parse 메소드는 다음과 같은 예외
타입을 던질 수 있다고 예외의 원인과 함께 설명되어 있다.
ArgumentNullException
FormatException
OverflowException
34/94
소프트웨어 엔지니어링 관점 13.1
35/94
프로그램은 빈번하게 리소스resource를 동적으로 요청하고 해제한다.
운영 체제는 일반적으로 하나의 파일(리소스)에 대해 두 개 이상의 프로그램이
조작 못하도록 한다.
따라서, 특정 프로그램이 특정 파일을 사용하기 위해서는 다른 프로그램이 이
파일을 닫아야 한다 (즉, 리소스를 해제해야 한다).
파일이 닫히지 않으면 리소스 누수resource leak가 발생한다.
36/94
오류 방지 방법 13.2
37/94
명시적으로 해제 되었어야 할 리소스가 앱에서 계속 점유되고 있으면 예외가
발생할 수 있다.
예외 발생 여부와 상관없이, 리소스가 더 이상 필요하지 않게 되면 반드시
해제해야 한다.
38/94
오류 방지 방법 13.3
39/94
성능 향상 팁 13.1
40/94
Fig.13.4에서는 예외가 해당 try 블록에서 발생하는지 여부에 관계없이 finally
블록이 항상 실행된다는 것을 보여준다.
41/94
42/94
43/94
44/94
45/94
46/94
47/94
48/94
49/94
50/94
throw 명령문이 실행된다는 것은 코드에서 문제가 발생했음을 나타낸다.
throw 명령문을 사용하여 예외 객체를 만들어 catch 블록으로 던진다.
throw 명령문의 피연산자를 통해 던져질 예외 객체를 생성한다. throw
명령문의 피연산자는 Exception 클래스 타입 또는 Exception 클래스의 파생
클래스 타입의 객체가 된다.
51/94
catch 블록에 던져진 예외 객체를 throw 명령문을 사용해 다시 던질 수 있다.
throw 명령문에 피연산자가 없는 경우, 던져진 예외 객체를 다음 순위의 catch 블록으로
다시 던진다.
throw 명령문에 피연산자가 있는 경우, 새로운 타입의 예외 객체가 생성되어
다음 순위의 catch 블록으로 던져진다.
이때, 이전에 던져진 예외 객체와 던져진 지점에 대한 정보는 스택 트레이스stack-
trace 에서 손실된다. 즉, 이전 예외 상황은 리셋reset 된다.
53/94
try 블록 끝까지 예외 발생 없이 정상적으로 수행되거나 또는 catch 블록에서
예외를 잡아 처리하면, 프로그램은 finally 블록을 경유해서 try-catch-finally
를 빠져나와 정상적인 실행을 계속한다.
catch 블록에서 예외를 잡지 못하거나 예외를 다시 던지면, 제어권은 이 try-
catch의 바깥쪽에 있는 catch 블록으로 들어간다.
55/94
소프트웨어 엔지니어링 관점 13.3
56/94
다음과 같은 코드에 대해
{
var exampleObject = new ExampleClass();
try
{
exampleObject.SomeMethod();
}
finally
{
if (exampleObject != null)
{
((IDisposable) exampleObject).Dispose(); // 리소스를 정리할 수 있는 Dispose 메소드
}
}
}
57/94
IDisposable 인터페이스를 구현한 클래스 타입으로 부터 생성된 객체는 using
명령문을 사용해 간단히 만들 수 있다.
using (var exampleObject = new ExampleClass())
{
exampleObject.SomeMethod();
}
58/94
Exception 클래스의 프러퍼티들은 잡힌 예외에 대한 오류 메시지를 만드는데
사용된다.
https://docs.microsoft.com/ko-kr/dotnet/api/system.exception?view=netcore-3.1
Message 프러퍼티는 Exception 객체와 관련된 오류 메시지를 저장한다.
한글 Windows OS는 한글 메시지 내용이 출력된다.
StackTrace 프러퍼티에는 메소드 호출 스택에 대한 내용을 나타내는 문자열이 들어 있다.
60/94
Exception 클래스는 그 밖에 다음과 같은 프러퍼티들을 제공한다.
HelpLink는 문제를 설명하는 도움말 파일의 위치를 지정한다.
Source는 예외를 발생시킨 앱 또는 객체의 이름을 지정한다.
TargetSite는 예외가 발생한 메소드를 지정한다.
61/94
Fig.13.5에서는 Exception 클래스의 프러퍼티들을 보여준다.
62/94
63/94
64/94
65/94
66/94
67/94
68/94
Exception 클래스의 생성자는 다음에 나오는 두 개의 인수를 받는다.
사용자 정의 에러 메시지와
추가 정보로 제공할 InnerException
69/94
Exception 클래스의 ToString 메소드는
예외 클래스의 이름과 Message 프러퍼티 값을 순서대로 연결한 단일 문자열을 리턴하여 첫
번째 출력 블록을 만드다.
두 번째 출력 블록에서는 InnerException 프러퍼티에 연결된 내부 예외 객체의 StackTrace
프러퍼티를 보여준다.
마지막 출력 블록은 ToString 메소드를 호출한 예외 객체의 StackTrace 프러퍼티를
보여준다.
StackTrace는 예외를 잡은 지점이 아닌 예외를 던진 지점에서의 메소드 호출
스택 상태를 나타낸다.
“at”으로 시작하는 StackTrace의 라인 번호는 호출 스택에 있는 메소드를
의미한다.
라인 번호를 통해 예외가 발생한 메소드, 이 메소드가 위치한 파일명, 그리고
예외를 던진 지점을 확인할 수 있다.
70/94
오류 방지 방법 13.5
71/94
특정 앱에 특화된 사용자 정의 예외 클래스를 만들 수 있다.
사용자 정의 예외 클래스는 Exception 클래스에서 직접 또는 간접적으로 파생
되어야 한다.
사용자 정의 예외는 다른 개발자가 예외 처리 방법을 알 수 있도록 문서화 되어
있어야 한다.
72/94
Fig.13.6-13.7은 사용자 정의 예외 클래스를 보여준다. 클래스에는 다음과 같은
세 종류의 생성자가 반드시 정의되어야 한다.
매개변수 없는 생성자
오류 메시지 내용에 대한 string 타입의 인수를 받는 생성자
오류 메시지 내용에 대한 string 타입의 인수와 내부 예외 객체에 대한 Exception 타입의
인수를 받는 생성자
73/94
좋은 프로그래밍 습관 13.1
74/94
소프트웨어 엔지니어링 관점 13.4
75/94
제곱근을 계산하는 NegativeNumberException 클래스(Fig.13.6)에서는 잘못
입력된 음수 값으로 인해 발생하는 예외를 처리한다.
SquareRootTest 클래스(Fig.13.7)에서는 사용자 정의 예외 클래스의 적용
과정을 보여준다.
76/94
77/94
78/94
79/94
80/94
81/94
82/94
참조변수를 사용하여 메소드를 호출하거나 프러퍼티에 액세스 하기 전에 이
참조변수가 null 인지 여부를 확인한다면 NullReferenceException을 피할 수
있다.
83/94
오류 방지 방법 13.6
84/94
널 조건부 연산자 (?.)를 사용해 참조변수가 null 인지 확인할 수 있다.
다음 명령문은 exampleObject가 null이 아닌 경우에만 Dispose 메소드를
호출한다.
exampleObject?.Dispose();
85/94
is 연산자와 함께 다운 캐스팅하면 InvalidCastException이 발생할 수 있다.
86/94
예를 들어, BasePlusCommissionEmployee 객체에 급여를 10% 인상하려면, 다음과 같은
명령문을 사용하여 employee가 null이 아닌 경우에만 BaseSalary 프러퍼티 값을
변경하도록 한다.
employee?.BaseSalary *= 1.10M;
87/94
employee?.BaseSalary 값을 읽기 위해 다음과 같은 명령문을 사용한다.
decimal salary = employee?.BaseSalary;
이 명령문에서는 decimal? 타입을 decimal 타입으로 묵시적 변환을 할 수 없음을 나타내는
컴파일 오류를 발생시킨다.
89/94
오류 방지 방법 13.7
90/94
널 병합 연산자null coalescing operator (??)는 null이 될 수 있는 값을 대상으로
동작한다.
널 병합 연산자는 두 개의 피연산자를 갖고 있다. 왼쪽 피연산자가 null이
아니면, ?? 표현식의 결과는 왼쪽 피연산자의 값으로 평가된다. 그렇지 않으면,
오른쪽 피연산자의 값으로 평가된다.
if (employee != null)
{
salary = employee.BaseSalary
}
92/94
C# 6 이전에는 예외 클래스 타입 만을 가지고 예외를 잡을 수 있었다.
C# 6에서는 예외 필터 개념을 도입하여 catch의 예외 타입과 when 절에
지정된 조건을 기반으로 예외를 잡을 수 있다.
catch(ExceptionType name) when(condition)
when 절의 조건이 true인 경우에만 예외를 잡는다.
94/94