---
title: "값은 그대로 두고 모양만 바꾼다"
description: "$ 는 문자열 안에 값을 끼워 넣고, 콜론 뒤에 적는 형식은 그 값을 어떻게 보여줄지만 정합니다. 캐스팅과 달리 값 자체는 건드리지 않습니다."
author: "유니얼"
pubDate: 2026-09-09
category: "programming/csharp/basics"
tags: ["C#", "기초"]
canonical: https://blog.unialgames.com/posts/csharp-string-interpolation/
---
앞에서 문자열 보간을 한 줄만 스치고 지나갔습니다.

```csharp
string msg = $"{name} 님의 점수는 {score}점입니다";
```

읽기 좋다는 말만 하고 넘어갔는데, 여기에는 두 가지 장치가 들어 있습니다.
**값을 끼워 넣는 것**과 **그 값을 어떤 모양으로 보여줄지 정하는 것**입니다.
둘은 다른 일이고, 헷갈리면 앞에서 본 형변환과 뒤섞이게 됩니다.

## 보간은 이렇게 생겼다

따옴표 앞에 `$` 를 붙이면 그 문자열 안에서 중괄호가 특별한 뜻을 갖습니다.
**중괄호 안에 적은 것이 값으로 바뀌어 그 자리에 들어갑니다.**

<figure class="diagram">
<svg viewBox="0 0 680 210" role="img" aria-label="달러 기호를 붙인 문자열에서 중괄호 안의 변수가 값으로 바뀌어 들어가는 그림">
<g font-family="var(--font-mono)" font-size="16">
<text x="30" y="40" fill="var(--accent)">$</text>
<text x="44" y="40" fill="var(--text-muted)">"Name: </text>
<text x="132" y="40" fill="var(--text)">{name}</text>
<text x="212" y="40" fill="var(--text-muted)">, Age: </text>
<text x="300" y="40" fill="var(--text)">{age}</text>
<text x="364" y="40" fill="var(--text-muted)">"</text>
</g>
<g stroke="var(--border)" stroke-width="1.4" fill="none">
<path d="M162 52 L162 84"></path>
<path d="M326 52 L326 84"></path>
</g>
<g fill="var(--bg)" stroke="var(--border)">
<rect x="112" y="84" width="100" height="42" rx="8"></rect>
<rect x="276" y="84" width="100" height="42" rx="8"></rect>
</g>
<g text-anchor="middle" font-family="var(--font-mono)" font-size="14">
<text x="162" y="111" fill="var(--accent)">"손오공"</text>
<text x="326" y="111" fill="var(--accent)">30</text>
</g>
<g stroke="var(--border)" stroke-width="1.4" fill="none">
<path d="M244 105 L268 105"></path>
</g>
<g stroke="var(--text-muted)" stroke-width="1.4" fill="none">
<path d="M400 105 L438 105"></path>
<path d="M432 100 L438 105 L432 110"></path>
</g>
<g fill="var(--accent-soft)" stroke="var(--accent)">
<rect x="448" y="84" width="210" height="42" rx="8"></rect>
</g>
<text x="553" y="111" text-anchor="middle" font-family="var(--font-mono)" font-size="14" fill="var(--accent)">Name: 손오공, Age: 30</text>
<text x="30" y="172" font-size="13" fill="var(--text-muted)">중괄호가 사라진 자리에 값이 들어갑니다. 원래 변수는 아무 영향도 받지 않습니다.</text>
</svg>
<figcaption>중괄호 안이 값으로 바뀌어 그 자리에 들어갑니다.</figcaption>
</figure>

```csharp
string name = "손오공";
int age = 30;

Console.WriteLine($"Name: {name}, Age: {age}");   // Name: 손오공, Age: 30
```

### 흔한 실수: `$` 를 빼먹는다

`$` 가 없으면 중괄호는 그냥 글자입니다.

```csharp
Console.WriteLine($"Age: {age}");    // Age: 30
Console.WriteLine("Age: {age}");     // Age: {age}
```

에러가 나지 않고 조용히 글자 그대로 찍히기 때문에, 출력이 이상할 때
가장 먼저 볼 자리입니다.

### 중괄호 안에는 식도 들어간다

변수 이름만 넣을 수 있는 게 아닙니다. 앞에서 본 대로 **식은 값 하나로 줄어드니까**,
값 하나로 줄어드는 것이면 무엇이든 들어갑니다.

```csharp
int a = 3, b = 4;

Console.WriteLine($"합은 {a + b}");                        // 합은 7
Console.WriteLine($"{(a > b ? "a 가 큼" : "b 가 큼")}");    // b 가 큼
Console.WriteLine($"이름 길이는 {name.Length}");            // 이름 길이는 3
```

### 중괄호 자체를 쓰려면 두 번

중괄호가 특별한 뜻을 가지니, 중괄호 문자 자체가 필요하면 두 번 씁니다.
역슬래시를 두 번 썼던 것과 같은 이유입니다.

```csharp
Console.WriteLine($"{{{name}}}");   // {손오공}
```

## 콜론 뒤는 모양이다

중괄호 안에 콜론을 찍고 형식을 적으면 **같은 값이 다른 모양으로 찍힙니다.**

```csharp
double pi = 3.14159265;

Console.WriteLine($"{pi}");      // 3.14159265
Console.WriteLine($"{pi:F2}");   // 3.14
```

자주 쓰는 것은 이 정도입니다.

| 형식 | 쓰는 법 | 결과 | 쓰임 |
| --- | --- | --- | --- |
| `F2` | `{pi:F2}` | `3.14` | 소수점 아래 자릿수 고정 |
| `N0` | `{amount:N0}` | `1,234,567` | 천 단위 쉼표 |
| `D8` | `{number:D8}` | `00001234` | 자릿수 고정, 앞을 0으로 채움 |
| `P0` | `{rate:P0}` | `45%` | 백분율 |
| `C` | `{price:C}` | `₩1,200` | 통화 |
| `yyyy-MM-dd` | `{today:yyyy-MM-dd}` | `2026-09-09` | 날짜 |

`F` 뒤의 숫자는 소수점 아래 자릿수, `D` 뒤의 숫자는 전체 자릿수입니다.

> `C` 와 날짜 형식은 실행하는 컴퓨터의 지역 설정을 따라갑니다.
> 같은 코드가 한국에서는 `₩1,200`, 미국에서는 `$1,200.00` 으로 찍힐 수 있습니다.
> 이 차이는 문자열 비교를 볼 때 다시 나옵니다.

## 값은 바뀌지 않는다

여기가 이 글의 요점입니다. **형식 지정자는 값을 건드리지 않습니다.**

```csharp
double pi = 3.14159265;

Console.WriteLine($"{pi:F0}");   // 3
Console.WriteLine(pi);           // 3.14159265   ← 그대로입니다
Console.WriteLine(pi * 2);       // 6.2831853    ← 계산도 원래 값으로
```

앞에서 본 캐스팅과 정반대입니다. 캐스팅은 **값을 실제로 잘라냅니다.**
`(int)pi` 를 담은 변수에는 소수부가 아예 없습니다. 되돌릴 수 없습니다.

<x-compare>
<script type="application/json">
{
  "label": "3.7 을 3으로 만드는 두 가지",
  "tabs": [
    { "label": "캐스팅", "code": "double x = 3.7;\nint    n = (int)x;\n\nConsole.WriteLine(n);       // 3\nConsole.WriteLine(n * 2);   // 6", "stats": [["바뀌는 것", "값 자체"], ["되돌리기", "불가능"], ["이후 계산", "3으로 계산됨"]], "tone": "bad" },
    { "label": "형식 지정자", "code": "double x = 3.7;\n\nConsole.WriteLine($\"{x:F0}\");   // 4\nConsole.WriteLine(x);            // 3.7\nConsole.WriteLine(x * 2);        // 7.4", "stats": [["바뀌는 것", "화면에 보이는 모양"], ["되돌리기", "필요 없음"], ["이후 계산", "3.7 로 계산됨"]], "tone": "good" }
  ]
}
</script>
</x-compare>

두 결과가 다른 것도 보입니다. `(int)` 는 앞에서 본 대로 **버림**이라 `3.7` 이 `3` 이 되고,
`F0` 은 **반올림**이라 `4` 로 보입니다. 하는 일이 아예 다르다는 뜻입니다.

> **화면에 보여줄 때만 형식 지정자를 쓰고, 값을 실제로 바꿔야 할 때만 캐스팅을 씁니다.**
> 소수 둘째 자리까지만 저장해야 한다면 `Math.Round` 로 값을 바꿔야 하고,
> 화면에만 그렇게 보이면 되는 것이면 `F2` 로 충분합니다.

## 자리 폭도 정할 수 있다

쉼표 뒤에 숫자를 적으면 그 폭만큼 자리를 잡습니다. 양수면 오른쪽 정렬, 음수면 왼쪽 정렬입니다.

```csharp
string[] names = { "이름", "손오공", "저팔계" };
string[] scores = { "점수", "95", "7" };

for (int i = 0; i < names.Length; i++)
{
    Console.WriteLine($"{names[i],-8}{scores[i],5}");
}
```

```text
이름         점수
손오공         95
저팔계          7
```

쉼표와 콜론을 같이 쓸 때는 **폭이 먼저, 형식이 나중**입니다.

```csharp
Console.WriteLine($"{price,10:N0}");   // 폭 10칸, 천 단위 쉼표
```

> 한글은 글자 하나가 알파벳 두 칸 너비로 보이는데 `Length` 는 1 로 세기 때문에,
> 한글이 섞인 표는 이 방법으로 딱 맞추기 어렵습니다. 앞에서 본 대로
> 글자 수와 화면에서 차지하는 너비는 다른 문제입니다.

## `+` 와 무엇이 다른가

결과는 같습니다. 컴파일러가 보간 문자열을 내부적으로 이어붙이는 코드로 바꿔주기 때문입니다.
그래서 앞에서 본 규칙이 그대로 적용됩니다. **한 줄 안에서 몇 번 이어붙이는 것은 괜찮고,
반복문 안에서 쌓는 것은 여전히 `StringBuilder`** 입니다.

보간을 쓰는 이유는 속도가 아니라 읽기 쉬움입니다.

```csharp
string a = "[" + level + "] " + name + " (" + score + "점)";
string b = $"[{level}] {name} ({score}점)";
```

따옴표와 `+` 를 세어 가며 읽지 않아도 결과 모양이 그대로 보입니다.

## 정리

- `$` 를 붙이면 중괄호 안이 값으로 바뀌어 그 자리에 들어간다.
- `$` 를 빼먹으면 에러 없이 중괄호가 글자 그대로 찍힌다.
- 중괄호 안에는 값 하나로 줄어드는 식이면 무엇이든 들어간다.
- 콜론 뒤에 적는 형식은 **보여주는 모양만** 정한다. 값은 그대로다.
- 캐스팅은 값을 실제로 자르고, 형식 지정자는 자르지 않는다. `(int)3.7` 은 3이고 `{3.7:F0}` 은 4다.
- 쉼표 뒤의 숫자는 자리 폭이다. 음수면 왼쪽 정렬. 폭이 먼저, 형식이 나중이다.
- 보간은 `+` 보다 빠른 게 아니라 읽기 쉬운 것이다. 반복문에서는 여전히 `StringBuilder`.