Download as pdf or txt
Download as pdf or txt
You are on page 1of 94

- Chapter 13 -

 예외exception가 무엇이고 어떻게 처리handling하는지 이해한다.


 어떤 상황에서 예외 처리를 사용하는지 알아본다.
 try 블록 내에 지정된 예외를 던질 수 있는 코드를 작성한다.
 런타임 시 발생한 문제를 나타내기 위해 throw를 사용한다.
 catch 블록 내에 예외 핸들러exception handler를 지정한다.
 예외를 잡지 못했을 때 어떤 상황이 발생하는지 이해한다.
 예외 처리에 대한 종료 모델의 메커니즘을 이해한다.
 finally 블록을 사용하여 리소스를 해제한다.
 using 명령문이 어떻게 리소스를 자동으로 해제하는지 살펴본다.
 닷넷 예외 클래스들의 계층 구조를 이해한다.

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

Parse 메소드는 문제가 발생하면 예외 처리를 해야 하지만, TryParse


메소드는 예외를 던지지 않고 조건문(if 명령문)에서 변환된 값에 대한
유효성 검사를 수행할 수 있다. 따라서 Parse 메소드 보다 TryParse
메소드를 사용하는 것이 편리하다.

22/94
 try 블록에는 예외를 던질 수 있는 코드와 예외 발생 시 건너 뛰어야 하는 코드를
포함하고 있다.

23/94
 try 블록에서 예외가 발생하면 예외 객체를 만들어, 이 객체에 대응되는 catch
블록으로 보내 처리한다.
 try 블록 바로 다음에 하나 이상의 catch 블록이 선언되어 있어야 한다.
 따라서, 하나의 try 블록과 다수의 catch 블록으로 한 쌍을 이룬다.

 catch 블록의 헤더에 catch 블록에서 처리할 수 있는 예외 객체에 대한


매개변수(참조변수)를 지정한다.
 모든 종류의 예외 타입을 잡기 위해서, 헤더가 없는 (즉, 매개변수가 없는) 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

catch 블록의 헤더에서 쉼표로 구분된 매개변수 목록을 지정하면 구문


오류가 발생한다. catch 블록은 오직 하나의 매개변수 만 가질 수 있다.
13.10 절에서는 예외 필터를 사용하여 예외를 잡을 수 있는 조건들을
지정하는 방법을 소개한다.

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

기반 클래스 타입의 예외를 잡는 catch 블록이 파생 클래스 타입의


예외를 잡는 catch 블록 보다 앞쪽에 배치하면 컴파일 오류가
발생한다. 파생 클래스 타입의 예외들을 잡는 catch 블록 보다 기반
클래스 타입의 예외를 잡는 catch 블록이 먼저 수행되기 때문에 파생
클래스 타입의 예외는 절대로 처리되지 않는다.

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

특정 메소드 내에서 예외 발생의 가능성이 높다고 판단되면, 이


메소드를 호출하는 호출자를 try 블록으로 묶어 예외를 잡아 처리할 수
있도록 해야 한다.

35/94
 프로그램은 빈번하게 리소스resource를 동적으로 요청하고 해제한다.
 운영 체제는 일반적으로 하나의 파일(리소스)에 대해 두 개 이상의 프로그램이
조작 못하도록 한다.
 따라서, 특정 프로그램이 특정 파일을 사용하기 위해서는 다른 프로그램이 이
파일을 닫아야 한다 (즉, 리소스를 해제해야 한다).
 파일이 닫히지 않으면 리소스 누수resource leak가 발생한다.

36/94
오류 방지 방법 13.2

CLR은 메모리 누수를 완전하게 제거하지 못한다. 특정 객체를


참조하는 참조변수가 하나도 없으면 그 객체는 쓰레기 수집기에 의해
처리된다. 메모리 누수가 발생하면 메모리가 필요할 때 지연delay이
발생할 수 있다. 따라서 더 이상 필요하지 않는 객체를 계속해서
참조하는 상태가 되면 메모리 누수가 발생할 수 있다.

37/94
 명시적으로 해제 되었어야 할 리소스가 앱에서 계속 점유되고 있으면 예외가
발생할 수 있다.
 예외 발생 여부와 상관없이, 리소스가 더 이상 필요하지 않게 되면 반드시
해제해야 한다.

 finally 블록은 예외 발생 여부에 관계없이 항상 실행된다.


 try 블록에서 생성된 리소스는 해당 finally 블록에서 해제하는 것이 이상적이다.
 try 블록에 선언된 지역 변수는 해당 finally 블록에서 액세스 할 수 없으므로, 두
블록 모두에서 액세스 할 수 있도록 지역변수를 try 블록 앞에 선언해야 한다.

38/94
오류 방지 방법 13.3

일반적으로 finally 블록에는 해당 try 블록에서 생성된 리소스를


해제하는 코드가 포함되어 있어야 한다. 따라서, finally 블록은 리소스
누수를 제거하는데 효과적인 메커니즘이 된다.

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 된다.

 이전 예외 상황을 리셋 하기 전에 이전 예외 객체를 새로운 타입의 예외 객체에


포함시킬 수 있다.
 새로운 타입의 예외 객체가 만들어지기 전에 존재하고 있는 이전 예외 객체를
내부 예외 객체 inner exception object라고 한다.
52/94
소프트웨어 엔지니어링 관점 13.2

일반적으로, 던져진 예외 객체를 그대로 다시 던지기 하는 것 보다,


새로운 예외 객체를 만들어 던지는 것이 좋다. 이때, 새로운 예외
객체의 생성자를 호출해서 내부 예외 객체 (던져진 예외 객체)를
인수로 전달하면, 기존 예외 상황에 대한 스택 트레이스 정보를 모두
유지할 수 있다. 13.7.3절에서 내부 예외 객체를 새로운 예외 객체의
생성자에 전달하는 방법을 소개한다.

53/94
 try 블록 끝까지 예외 발생 없이 정상적으로 수행되거나 또는 catch 블록에서
예외를 잡아 처리하면, 프로그램은 finally 블록을 경유해서 try-catch-finally
를 빠져나와 정상적인 실행을 계속한다.
 catch 블록에서 예외를 잡지 못하거나 예외를 다시 던지면, 제어권은 이 try-
catch의 바깥쪽에 있는 catch 블록으로 들어간다.

 try 블록 안에 또다른 try-catch를 중첩하는 것도 가능하다. 이 경우, 바깥쪽 try


블록에 붙어있는 catch 블록은 안쪽 try-catch에서 잡아 처리 못한 예외를
처리한다.
 try 블록에 return 명령문이 있어 이 명령문으로 인해 try 블록이 종료되더라도
try 블록과 짝을 이루는 finally 블록은 반드시 실행된다.
 리턴은 finally 블록이 실행된 이후 발생한다.
54/94
일반적인 프로그래밍 오류 13.3

finally 블록이 실행될 때 잡히지 않은 예외가 처리 대기 중에 있는


상태에서 finally 블록에서 새로운 예외 객체를 만들어 던지면, 대기
중인 예외 객체는 손실되고 새로운 예외 객체만 try-catch-finally
바깥쪽에 있는 catch 블록으로 들어간다.

55/94
소프트웨어 엔지니어링 관점 13.3

예외가 발생할 수 있는 모든 명령문 주위에 try 블록을 배치하는 것은


좋지 않다. 만약 그렇게 하면 가독성이 떨어지게 된다. 대신,
중요하다고 판단되는 코드 일부분에 대해 하나의 try 블록 배치하고,
예상되는 예외 상황들을 처리할 수 있도록 여러 개의 catch 블록을 try
블록 다음에 위치시키고, 마지막으로 하나의 finally 블록이 오도록
한다. 다수의 명령문에서 동일한 예외 타입을 던질 경우, 각각의
명령문에 대해 try 블록을 사용하는 것이 좋다.

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 프러퍼티에는 메소드 호출 스택에 대한 내용을 나타내는 문자열이 들어 있다.

 IDE는 PDB 파일을 생성하여 프로젝트의 디버깅 정보를 관리한다.


 IDE는 디버깅 정보에 액세스 하여 메소드 호출 스택 상에 있는 메소드들의 라인
번호를 스택 트레이스stack trace에 기록한다. 스택 트레이스의 첫 번째 라인
번호는 예외가 던져진 지점을 나타내며, 그 뒤에 나오는 라인 번호들은 스택
트레이스에서 어떤 메소드들이 호출되었는지 그 위치를 나타낸다.
59/94
 예외가 발생하면, 프로그래머는 이 예외에 대해 자신이 정의한 오류 메시지로
표현할 수 있고 또는 새로운 예외 타입으로도 만들 수 있다.
 내부 예외 객체는 InnerException 프러퍼티에 할당된다.

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

예외를 잡아서 다시 던지기를 할 때, 다시 던지려고 하는 예외 객체에


디버깅 정보를 추가로 제공하는 것이 좋다. 처음 던져진 예외 객체의
클래스 타입 보다 상위 클래스 타입으로 객체를 생성한 후 이 객체의
생성자에 내부 예외 객체를 인수로 전달하여 InnerException
프러퍼티를 초기화 한다.

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

참조변수를 사용하여 객체의 메소드를 호출하거나 프러퍼티에 액세스


하기 전에 이 참조변수가 null 인지 여부를 항상 확인해야 한다.

84/94
 널 조건부 연산자 (?.)를 사용해 참조변수가 null 인지 확인할 수 있다.
 다음 명령문은 exampleObject가 null이 아닌 경우에만 Dispose 메소드를
호출한다.
exampleObject?.Dispose();

 이렇게 하면, if 명령문을 통해 exampleObject가 null 인지 여부를 확인할 필요


없다.

85/94
 is 연산자와 함께 다운 캐스팅하면 InvalidCastException이 발생할 수 있다.

 as 연산자를 사용하면 InvalidCastException을 피할 수 있다.


var employee = currentEmployee as BasePlusCommissionEmployee;

 참조변수인 currentEmployee가 참조하는 객체가 BasePlusCommissionEmployee


타입인 경우, 참조변수 employee 의 타입은 BasePlusCommissionEmployee가 되며
currentEmployee가 가르키는 객체의 주소가 할당된다.
 만약 currentEmployee가 참조하는 객체가 BasePlusCommissionEmployee 타입이
아니면, BasePlusCommissionEmployee 타입의 참조변수 employee는 null이 할당된다.
 employee는 null 일 수 있으므로 사용하기 전에 반드시 확인해야한다.

86/94
 예를 들어, BasePlusCommissionEmployee 객체에 급여를 10% 인상하려면, 다음과 같은
명령문을 사용하여 employee가 null이 아닌 경우에만 BaseSalary 프러퍼티 값을
변경하도록 한다.
employee?.BaseSalary *= 1.10M;

87/94
 employee?.BaseSalary 값을 읽기 위해 다음과 같은 명령문을 사용한다.
decimal salary = employee?.BaseSalary;
 이 명령문에서는 decimal? 타입을 decimal 타입으로 묵시적 변환을 할 수 없음을 나타내는
컴파일 오류를 발생시킨다.

 일반적으로 값 타입 변수에는 null을 할당 할 수 없다.


 employee 참조변수가 null 일 수도 있기 때문에, 이 표현식에서는 널 허용
타입을 리턴 할 수 있도록 해야 한다. 널 허용 타입은 값 타입이 null이 될 수
있음을 의미한다.
 값 타입 다음에 물음표(?)를 넣어 널 허용 타입을 지정한다. 따라서,
decimal?은 널 허용 decimal 타입을 의미한다.
decimal? salary = employee?.BaseSalary;
 employee의 BaseSalary 값 또는 null 을 salary에 할당할 수 있다. 88/94
 널 허용 타입은 다음과 같은 메소드와 프러퍼티를 포함하고 있다.
 GetValueOrDefault 메소드는 널 허용 타입 변수가 값을 갖고 있는지 여부를 확인한다. 만약
값을 갖고 있다면 이 메소드는 그 값을 리턴하고, 그렇지 않다면 해당 값 타입의 디폴트 값을
리턴한다.
 GetValueOrDefault 메소드의 또 다른 오버로드 메소드는 사용자가 정의한 디폴트 값을 받을
수 있도록 한 개의 매개변수가 지정되어 있다.
 HasValue 프러퍼티는 널 허용 타입 변수가 값을 갖고 있으면 true를 리턴하고 그렇지 않으면
false를 리턴한다.
 Value 프러퍼티는 널 허용 타입 변수의 값을 리턴하거나 원본 값이 null인 경우
InvalidOperationException을 던진다.

 널 허용 타입의 변수는 널 조건부 연산자 (?.) 또는 널 병합 연산자null coalescing


operator (??)의 왼쪽 피연산자로 사용할 수 있다.

89/94
오류 방지 방법 13.7

널 허용 타입 변수의 Value 프러퍼티를 사용하기 전에 HasValue


프러퍼티를 사용하여 변수에 값이 있는지 확인한다. 널 허용 타입
변수가 null인 경우, Value 프러퍼티에 접근하면
InvalidOperationException이 발생한다.

90/94
 널 병합 연산자null coalescing operator (??)는 null이 될 수 있는 값을 대상으로
동작한다.
 널 병합 연산자는 두 개의 피연산자를 갖고 있다. 왼쪽 피연산자가 null이
아니면, ?? 표현식의 결과는 왼쪽 피연산자의 값으로 평가된다. 그렇지 않으면,
오른쪽 피연산자의 값으로 평가된다.

decimal salary = employee?.BaseSalary ?? 0M;


 employee가 null이 아닌 경우, employee의 BaseSalary 프러퍼티의 값이 salary에
할당된다. 그렇지 않으면, salary에 0M이 할당된다.

 위 명령문은 다음과 동일하다.


decimal salary = (employee?.BaseSalary).GetValueOrDefault();
91/94
 다음 코드와 같이 null을 명시적으로 테스트 하는 것 보다 널 병합 연산자를
사용하면 간결한 코드를 작성할 수 있다.
decimal salary = 0M;

if (employee != null)
{
salary = employee.BaseSalary
}

92/94
 C# 6 이전에는 예외 클래스 타입 만을 가지고 예외를 잡을 수 있었다.
 C# 6에서는 예외 필터 개념을 도입하여 catch의 예외 타입과 when 절에
지정된 조건을 기반으로 예외를 잡을 수 있다.
catch(ExceptionType name) when(condition)
 when 절의 조건이 true인 경우에만 예외를 잡는다.

 다음과 같이 예외 타입을 제공하지 않는 catch 절에 대해서도 예외 필터를


지정할 수 있다. 이렇게 하면, 조건을 기반으로 예외를 잡을 수 있다.
catch when(condition)

 예외 필터의 사용 목적은 예외 객체의 프러퍼티가 특정 값을 갖고 있는지


판단하기 위함이다.
93/94
일반적인 프로그래밍 오류 13.4

동일 타입의 예외를 처리할 수 있는 여러 개의 catch 절을 try 블록


다음에 위치시키면 컴파일 오류가 발생한다. 만약 이들 catch 절이
서로 다른 when 절을 제공하고 있다면 컴파일 오류는 발생하지 않는다.
when 절을 갖고 있는 catch 절과 when 절을 갖고 있지 않는 catch
절이 동시에 있을 때, when 절을 갖고 있지 않는 catch 절을 마지막에
위치시킨다. 그렇지 않으면 컴파일 오류가 발생한다.

94/94

You might also like