---
title: "코드에 적히지 않은 약속은 없는 약속이다"
description: "case 4 가 목요일이라는 사실이 코드 어디에도 없습니다. const 와 enum 은 그 약속을 코드 안으로 옮기는 문법입니다."
author: "유니얼"
pubDate: 2026-09-08
category: "programming/csharp/basics"
tags: ["C#", "기초"]
canonical: https://blog.unialgames.com/posts/csharp-const-and-enum/
---
앞 글에서 이런 코드를 썼습니다.

```csharp
switch (day)
{
    case 4:
        Console.WriteLine("목요일");
        break;
}
```

돌아가는 데는 문제가 없습니다. 문제는 반년 뒤입니다.

`day` 에 들어 있는 `4` 가 목요일이라는 약속은 **지금 코드 어디에도 없습니다.**
사람 머릿속에만 있습니다. 한 주가 일요일부터인지 월요일부터인지,
0부터 세는지 1부터 세는지도 코드를 아무리 봐도 안 나옵니다.

> **뜻이 있는 값에는 이름을 붙입니다. 그래야 약속이 코드 안에 남습니다.**

## const 는 이렇게 생겼다

바뀌지 않는 값 하나에 이름을 붙이는 문법입니다.

```csharp
const double Pi = 3.14159;

Console.WriteLine(Pi * r * r);
```

`const` 를 붙이면 두 가지가 강제됩니다.

```csharp
const int MaxHp;        // 컴파일 에러: 선언할 때 값을 줘야 합니다

const int MaxHp = 100;
MaxHp = 200;            // 컴파일 에러: 나중에 바꿀 수 없습니다
```

앞에서 선언은 컴파일 시점, 할당은 실행 시점이라고 했습니다.
`const` 는 그 둘이 **컴파일 시점 한 번**으로 합쳐진 것입니다.
그래서 컴파일할 때 이미 알 수 있는 값만 들어갈 수 있습니다.
실행해 봐야 아는 값, 예를 들어 사용자가 입력한 숫자는 `const` 가 될 수 없습니다.

## 숫자를 그대로 쓰면 무슨 일이 생기나

```csharp
if (hp <= 30)
{
    ShowWarning();
}

if (damage > 30)
{
    PlayHitEffect();
}
```

두 개의 `30` 은 뜻이 다릅니다. 하나는 경고를 띄우는 체력 기준이고,
하나는 큰 피해를 판정하는 기준입니다. 코드에는 똑같이 `30` 이라고만 적혀 있습니다.

경고 기준을 40으로 올리라는 요청이 오면, 파일에서 `30` 을 찾아
**어느 쪽이 체력인지 하나씩 읽어 보고** 고쳐야 합니다. 하나를 잘못 고치면
경고는 그대로고 타격 효과만 조용히 바뀝니다.

```csharp
const int WarningHp = 30;
const int BigDamage = 30;
```

이름을 붙이면 찾을 것이 없습니다. 고칠 자리가 한 곳이고, 그 한 줄만 바꾸면 됩니다.

## enum 은 이렇게 생겼다

`const` 는 값 하나에 이름을 붙입니다.
그런데 요일처럼 **정해진 선택지가 묶음으로 있는** 경우가 있습니다.
이때 쓰는 것이 `enum` 입니다.

```csharp
enum Days { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday }

Days today = Days.Monday;

Console.WriteLine(today);        // Monday
```

이름만 나열했는데 값은 어디서 나오느냐면, 컴파일러가 0부터 붙여 줍니다.

<figure class="diagram">
<svg viewBox="0 0 680 128" role="img" aria-label="enum 의 각 이름에 0부터 6까지 번호가 자동으로 붙는 그림. Monday 가 1번으로 표시되어 있다">
<g fill="var(--bg)" stroke="var(--border)">
<rect x="8" y="26" width="88" height="58" rx="6"></rect>
<rect x="200" y="26" width="88" height="58" rx="6"></rect>
<rect x="296" y="26" width="88" height="58" rx="6"></rect>
<rect x="392" y="26" width="88" height="58" rx="6"></rect>
<rect x="488" y="26" width="88" height="58" rx="6"></rect>
<rect x="584" y="26" width="88" height="58" rx="6"></rect>
</g>
<rect x="104" y="26" width="88" height="58" rx="6" fill="var(--accent-soft)" stroke="var(--accent)" stroke-width="1.6"></rect>
<g text-anchor="middle" font-size="13" fill="var(--text-muted)" class="bit">
<text x="52" y="50">0</text>
<text x="244" y="50">2</text>
<text x="340" y="50">3</text>
<text x="436" y="50">4</text>
<text x="532" y="50">5</text>
<text x="628" y="50">6</text>
</g>
<text x="148" y="50" text-anchor="middle" font-size="13" fill="var(--accent)" class="bit">1</text>
<g text-anchor="middle" font-size="12" fill="var(--text)">
<text x="52" y="72">Sunday</text>
<text x="244" y="72">Tuesday</text>
<text x="340" y="72">Wednesday</text>
<text x="436" y="72">Thursday</text>
<text x="532" y="72">Friday</text>
<text x="628" y="72">Saturday</text>
</g>
<text x="148" y="72" text-anchor="middle" font-size="12" fill="var(--accent)" font-weight="600">Monday</text>
<text x="340" y="112" text-anchor="middle" font-size="13" fill="var(--text-muted)">적은 순서대로 0부터 번호가 붙는다</text>
</svg>
<figcaption>Days.Monday 는 사실 1이다.</figcaption>
</figure>

번호가 마음에 안 들면 직접 적어도 됩니다.

```csharp
enum Days { Sunday = 1, Monday, Tuesday }   // 1, 2, 3
```

하나만 적으면 그다음부터는 이어서 붙습니다.

## enum 과 switch 는 짝이다

이제 앞 글의 코드를 다시 씁니다.

```csharp
switch (today)
{
    case Days.Monday:
        Console.WriteLine("월요일");
        break;
    case Days.Thursday:
        Console.WriteLine("목요일");
        break;
}
```

`case 4:` 였을 때와 하는 일이 똑같습니다. 달라진 것은 읽는 사람입니다.
주석도 문서도 없이 코드만 보고 뜻을 압니다.

`enum` 은 후보가 정해져 있고 `switch` 는 후보를 늘어놓는 문법이라 서로 잘 맞습니다.
`Days` 를 하나 늘리면 `switch` 에 `case` 를 하나 더 적으면 되고,
그 자리를 빠뜨렸는지도 눈으로 셀 수 있습니다.

## enum 도 결국 정수다

여기서 한 가지 짚고 넘어가야 합니다.
`enum` 은 새로운 종류의 값이 아니라 **정수에 이름을 씌운 것**입니다.
바탕에 깔린 값은 그대로 `int` 입니다.

```csharp
Console.WriteLine((int)Days.Thursday);   // 4
```

그래서 이런 것도 컴파일이 됩니다.

```csharp
Days today = (Days)99;
```

`Days` 에 99번은 없습니다. 그래도 에러가 나지 않습니다.
앞에서 본 대로 캐스팅은 "내가 책임진다"는 표시이고,
컴파일러는 여기서 값의 범위를 확인해 주지 않습니다.

<x-flow>
<script type="application/json">
{
  "label": "enum 값이 후보 밖일 때",
  "lines": [
    "switch (today)",
    "{",
    "    case Days.Monday:",
    "        Console.WriteLine(\"월요일\");",
    "        break;",
    "    case Days.Thursday:",
    "        Console.WriteLine(\"목요일\");",
    "        break;",
    "    default:",
    "        Console.WriteLine(\"모르는 값: \" + (int)today);",
    "        break;",
    "}"
  ],
  "cases": [
    {
      "label": "Days.Thursday",
      "state": "today = Days.Thursday · 바탕 값 4",
      "path": [
        { "line": 0, "why": "맞춰 보는 것은 이름이 아니라 그 아래 깔린 정수입니다.", "verdict": "4" },
        { "line": 5, "why": "4 를 가진 case 로 갑니다." },
        { "line": 6, "why": "여기까지는 앞 글과 같습니다.", "out": "목요일" },
        { "line": 7, "why": "break 로 나갑니다." }
      ],
      "skip": [2, 3, 4, 8, 9, 10]
    },
    {
      "label": "(Days)99",
      "state": "today = (Days)99 · 바탕 값 99",
      "path": [
        { "line": 0, "why": "캐스팅으로 만든 값입니다. 컴파일러는 막지 않았습니다.", "verdict": "99" },
        { "line": 8, "why": "99 를 가진 case 가 없으니 default 로 옵니다." },
        { "line": 9, "why": "default 를 안 적어 뒀다면 여기서 아무 일도 일어나지 않습니다.", "out": "모르는 값: 99" },
        { "line": 10, "why": "그리고 switch 를 빠져나옵니다." }
      ],
      "skip": [2, 3, 4, 5, 6, 7],
      "note": "이름을 붙여도 바탕은 정수라, 후보 밖의 값이 들어올 자리가 남아 있습니다."
    }
  ]
}
</script>
</x-flow>

앞 글에서 `default` 를 적어 두라고 한 이유가 여기입니다.
`enum` 을 쓰면 후보가 다 막힌 것처럼 보이지만, 실제로 막힌 것은
**실수로 다른 값을 적는 일**이지 다른 값이 들어오는 일이 아닙니다.

## 정리

- 뜻이 있는 값을 숫자 그대로 쓰면, 그 뜻은 코드 밖에만 남습니다.
- `const` 는 값 하나에 이름을 붙입니다. 선언할 때 값을 줘야 하고 나중에 못 바꿉니다.
- `const` 에는 컴파일할 때 이미 아는 값만 들어갑니다.
- `enum` 은 정해진 선택지 묶음에 이름을 붙입니다. 적은 순서대로 0부터 번호가 붙습니다.
- `enum` 과 `switch` 는 짝입니다. 후보가 정해져 있다는 성질이 같습니다.
- `enum` 의 바탕은 정수입니다. 캐스팅으로 후보 밖의 값도 만들어지므로 `default` 는 남겨 둡니다.