C#LAB
C# 1.10 · 1부. 기초

예외 처리 기초

try-catch-finally 로 잘못된 상황을 다룹니다.

예상 학습 시간 14분 실행 단추가 있는 예제는 고쳐서 실행해 볼 수 있습니다 난이도 입문
개념 설명

잘못될 수 있는 일 다루기

문법이 맞고 컴파일도 되었는데 실행 중에 무너지는 일이 있습니다. 숫자가 아닌 글을 숫자로 바꾸려 하거나, 배열에 없는 자리를 건드리거나, 열려던 파일이 없을 때입니다. 이런 일이 생기면 C#은 예외(Exception)를 던집니다.

던져진 예외를 아무도 받지 않으면 프로그램은 그 자리에서 멈춥니다. try-catch 는 그것을 받아 내는 그물입니다. 받아 두면 무너지는 대신 어떻게 할지 정할 수 있습니다.

  • try — 잘못될 수 있는 코드를 여기 둡니다.
  • catch — 문제가 생겼을 때 할 일을 적습니다.
  • finally — 잘되든 잘못되든 반드시 할 일을 적습니다.
최소 예제 · 편집 가능

멈추지 않고 넘어가기

아래 코드는 브라우저 안에서 실제로 실행됩니다. 고쳐서 눌러 보셔도 됩니다.

Program.cs
string input = "abc";

try {
    int n = int.Parse(input);
    Console.WriteLine(n);
} catch (FormatException) {
    Console.WriteLine("숫자가 아닙니다.");
}

Console.WriteLine("계속 실행됩니다.");
출력
숫자가 아닙니다. 계속 실행됩니다.

코드는 고쳐서 실행해 볼 수 있습니다. 처음 누를 때만 실행기를 내려받느라 잠시 걸립니다. 적은 코드는 서버로 나가지 않습니다.

눈여겨볼 것은 마지막 줄이 실행되었다는 점입니다. catch 가 없었다면 프로그램은 int.Parse 에서 멈추고 그 아래로 내려오지 못했을 것입니다.

try 안에서 문제가 생기면 그 줄에서 곧바로 catch 로 건너뜁니다. 위 코드에서 Console.WriteLine(n) 이 실행되지 않은 것은 그 때문입니다.

상세 사용법

자주 만나는 예외

예외언제 나는가예시
FormatException숫자로 바꿀 수 없는 글을 바꾸려 할 때int.Parse("abc")
IndexOutOfRangeException배열에 없는 자리를 건드릴 때arr[5] (크기가 3)
NullReferenceException값이 없는 것을 통해 무언가 할 때name.Trim() (name 이 null)
DivideByZeroException정수를 0으로 나눌 때10 / 0
ArgumentException넘긴 값이 규칙에 맞지 않을 때메서드가 스스로 던집니다

10 / 0 은 예외가 나지만 10.0 / 0 은 나지 않고 무한대가 됩니다. 정수와 실수가 0을 다르게 다루기 때문입니다. 1.4에서 본 정수 나눗셈과 같은 결의 이야기입니다.

여러 가지를 받을 때는 좁은 것부터

catch 는 여러 개를 이어 둘 수 있습니다. 이때 위에서부터 차례로 맞는 것을 찾아 하나만 실행합니다. 그래서 좁은 것(구체적인 예외)을 먼저, 넓은 것을 나중에 두어야 합니다.

try {
    int n = int.Parse(input);
} catch (FormatException) {
    Console.WriteLine("숫자가 아닙니다.");
} catch (Exception ex) {          // 그 밖의 모든 것
    Console.WriteLine(ex.Message);
}

순서를 뒤집으면 Exception 이 모든 것을 먼저 받아 버려 아래가 쓸모없어집니다. 다행히 이 경우는 컴파일 오류로 걸리므로 실행 전에 알 수 있습니다.

ex.Message 는 무엇이 잘못됐는지 적힌 글입니다. 무슨 일이 있었는지 남길 때 사용합니다.

반드시 해야 할 일은 finally 에

파일이나 연결처럼 다 쓰고 반드시 닫아야 하는 것이 있습니다. finally 는 잘되든 예외가 나든 실행되므로 그 자리에 알맞습니다. try 안에서 값을 돌려주더라도 finally 는 실행된 뒤에 돌아갑니다.

try {
    // 파일을 열고 읽습니다
} catch (Exception ex) {
    Console.WriteLine(ex.Message);
} finally {
    Console.WriteLine("여기는 어떤 경우에도 지납니다.");
}

예외로 다룰 일과 아닌 일

예외는 뜻밖의 일을 위한 것입니다. 사람이 숫자가 아닌 것을 적는 일은 뜻밖이 아니라 늘 있는 일이므로, 그때는 예외를 던지고 받는 대신 1.4에서 본 TryParse 로 미리 걸러 내는 편이 낫습니다. 예외를 던지고 받는 데는 값이 들기도 하고, 무엇보다 코드가 말하는 바가 달라집니다.

예외로 받기 — 드문 상황으로 취급
try {
    int n = int.Parse(input);
    Console.WriteLine(n);
} catch (FormatException) {
    Console.WriteLine("숫자가 아닙니다.");
}
미리 확인하기 — 흔한 상황으로 취급
if (int.TryParse(input, out int n)) {
    Console.WriteLine(n);
} else {
    Console.WriteLine("숫자가 아닙니다.");
}

그리고 받아 놓고 아무것도 하지 않는 것은 가장 나쁩니다. 아래처럼 비워 두면 문제가 조용히 묻혀, 나중에 엉뚱한 곳에서 원인을 찾게 됩니다. 적어도 무슨 일이 있었는지는 남기세요.

try {
    // ...
} catch (Exception) {
    // 아무것도 하지 않음 — 문제가 사라진 것이 아니라 감춰집니다
}
버전 배지

이 문법, 몇 버전부터 사용할 수 있나요

1.0
2.0
3.0
6.0
7.0
8.0
9.0
12.0
14.0
  • 1.0try · catch · finally
  • 6.0when — 조건이 맞을 때만 받기
  • 8.0using 선언 — 닫는 일을 맡기기
C# 1.0+

try-catch-finally 는 처음부터 있었고 지금까지 문법이 바뀌지 않았습니다. 뒤에 더해진 것은 받는 조건을 좁히는 when(C# 6.0)과, 다 쓴 것을 닫는 일을 아예 맡겨 버리는 using 선언(C# 8.0)입니다. using 을 사용하면 finally 에 닫는 코드를 손수 적지 않아도 되는데, 그 이야기는 3부의 파일 다루기에서 다시 만납니다.

실습 문제

직접 해보기

1. 나눗셈을 안전하게 난이도 하

int a = 10, b = 0; 을 두고 a / b 를 시도하되, 0으로 나누는 경우에는 0으로 나눌 수 없습니다. 를 출력하고 프로그램이 멈추지 않게 해보세요.

정수를 0으로 나눌 때 나는 예외는 DivideByZeroException 입니다. 사실 이 경우는 나누기 전에 b == 0 인지 보는 편이 더 낫습니다 — 늘 있을 수 있는 일이기 때문입니다.
int a = 10, b = 0; try { Console.WriteLine(a / b); } catch (DivideByZeroException) { Console.WriteLine("0으로 나눌 수 없습니다."); } // 미리 걸러 내는 쪽 if (b == 0) Console.WriteLine("0으로 나눌 수 없습니다."); else Console.WriteLine(a / b);
2. 어떤 값이 와도 버티기 난이도 중

string[] inputs = { "10", "abc", "7" }; 를 탐색하면서 숫자인 것만 더하고, 숫자가 아닌 것은 건너뛰어 합계를 출력해보세요. 결과는 17 이 됩니다.

숫자가 아닌 값이 섞여 있는 것은 뜻밖의 일이 아니므로 TryParse 가 어울립니다. 1.6의 continue 를 함께 사용해도 좋습니다.
string[] inputs = { "10", "abc", "7" }; int sum = 0; foreach (string s in inputs) { if (!int.TryParse(s, out int n)) continue; sum += n; } Console.WriteLine(sum); // 17
요약
  • 예외를 받지 않으면 프로그램은 그 자리에서 멈춥니다.
  • catch좁은 것부터 적습니다. 넓은 것을 위에 두면 아래가 쓸모없어집니다.
  • finally어떤 경우에도 지납니다. 닫아야 할 것을 여기서 닫습니다.
  • 늘 있는 일은 예외로 다루지 않습니다. 미리 걸러 내는 쪽이 낫습니다.

C# 버전별 변경 이력 에서 각 버전이 무엇을 더했는지 볼 수 있습니다.