---
title: "적은 그대로가 값이 되는 문자열"
description: "따옴표는 문자열의 끝을 알리는 기호라서 안에 그대로 쓸 수 없습니다. 역슬래시로 피하는 방법과, 그마저 필요 없게 만드는 따옴표 세 개를 봅니다."
author: "유니얼"
pubDate: 2026-09-09
category: "programming/csharp/basics"
tags: ["C#", "기초"]
canonical: https://blog.unialgames.com/posts/csharp-raw-string/
---
앞 글에서 값은 원하는 대로 끼워 넣을 수 있게 됐습니다. 그런데 이건 안 됩니다.

```csharp
string s = "He said "Hi"";   // 컴파일 에러
```

컴파일러 입장에서 보면 당연합니다. 따옴표는 **문자열이 여기서 끝난다**는 표시입니다.
두 번째 따옴표를 만나는 순간 문자열은 `"He said "` 로 끝났고,
그다음의 `Hi` 는 갑자기 튀어나온 정체불명의 이름이 됩니다.

같은 문제가 역슬래시에도 있습니다. 역슬래시 역시 문법에 쓰이는 기호이기 때문입니다.

## 역슬래시는 "이건 문법이 아니다" 라는 표시

앞에서 이스케이프 문자를 봤습니다. `\n` 은 줄바꿈, `\t` 는 탭이었습니다.
같은 장치를 반대 방향으로도 씁니다. **문법 기호를 그냥 글자로 읽으라고 알릴 때**입니다.

```csharp
string s = "He said \"Hi\"";   // He said "Hi"
string p = "C:\\Users";        // C:\Users
```

`\"` 는 "이 따옴표는 문자열의 끝이 아니라 따옴표 글자다" 라는 뜻이고,
`\\` 는 "이 역슬래시는 명령이 아니라 역슬래시 글자다" 라는 뜻입니다.

한 가지 확인하고 넘어갈 게 있습니다. **역슬래시와 그다음 글자는 합쳐서 문자 하나입니다.**
소스코드에 두 글자로 적혀 있어도 메모리에는 한 칸만 차지합니다.

```csharp
Console.WriteLine("A\nB".Length);    // 3   A, 줄바꿈, B
Console.WriteLine("C:\\x".Length);   // 4   C, :, \, x
```

앞에서 문자열은 `char` 가 나란히 선 것이라고 했습니다. `"A\nB"` 의 칸 세 개 중 가운데는
아스키 10번, 앞에서 본 모양 없는 문자입니다. 화면에서는 줄이 바뀌는 것으로만 보입니다.

## `@` 는 역슬래시까지만 해결한다

앞에서 `@` 를 봤습니다. 앞에 붙이면 역슬래시를 특별하게 보지 않습니다.

```csharp
string p1 = "C:\\Users\\John\\Documents";
string p2 = @"C:\Users\John\Documents";     // 같은 값, 훨씬 읽기 쉽다
```

경로에는 확실히 편합니다. 그런데 **따옴표 문제는 그대로 남습니다.**
`@` 문자열 안에서 따옴표를 쓰려면 역슬래시 대신 따옴표를 두 번 써야 합니다.

```csharp
string s = @"He said ""Hi""";   // He said "Hi"
```

역슬래시가 사라진 대신 따옴표가 두 겹이 됐습니다. 규칙이 하나 줄고 하나 늘었을 뿐입니다.
JSON 처럼 따옴표가 촘촘한 텍스트에서는 여전히 읽기 어렵습니다.

## 따옴표 세 개를 쓰면 그대로 쓸 수 있다

C# 11 부터 **원시 문자열 리터럴**을 쓸 수 있습니다. 따옴표 세 개로 열고 닫습니다.
그 안에서는 역슬래시도 따옴표도 특별한 뜻이 없습니다. **적은 그대로가 값입니다.**

<x-compare>
<script type="application/json">
{
  "label": "같은 JSON 을 문자열로 담기",
  "tabs": [
    { "label": "이스케이프로", "code": "string json =\n  \"{\\n\" +\n  \"  \\\"name\\\": \\\"John\\\",\\n\" +\n  \"  \\\"age\\\": 30\\n\" +\n  \"}\";", "stats": [["원본과 같은 모양인가", "아니오"], ["따옴표마다", "역슬래시 하나씩"], ["줄바꿈", "직접 \\n 으로"]], "tone": "bad" },
    { "label": "따옴표 세 개로", "code": "string json = \"\"\"\n    {\n      \"name\": \"John\",\n      \"age\": 30\n    }\n    \"\"\";", "stats": [["원본과 같은 모양인가", "예"], ["따옴표마다", "그대로"], ["줄바꿈", "줄을 바꾸면 됨"]], "tone": "good" }
  ]
}
</script>
</x-compare>

오른쪽은 다른 데서 복사해 온 JSON 을 그대로 붙여넣은 것입니다.
고칠 게 없다는 것이 이 문법의 전부입니다.

### 규칙 세 가지

**1. 여는 따옴표 뒤와 닫는 따옴표 앞은 줄을 바꿉니다.**

여러 줄로 쓸 때 여는 `"""` 와 같은 줄에 내용을 적을 수 없습니다.
첫 줄바꿈과 마지막 줄바꿈은 값에 들어가지 않습니다.

**2. 닫는 따옴표의 들여쓰기가 기준선입니다.**

이게 처음에 놀라는 부분입니다. 코드 안에서 들여쓴 만큼이 값에 그대로 들어가면
곤란하니, **닫는 따옴표가 있는 칸까지는 모든 줄에서 잘라냅니다.**

<figure class="diagram">
<svg viewBox="0 0 680 250" role="img" aria-label="닫는 따옴표의 들여쓰기 위치가 기준선이 되어 그 왼쪽 공백이 모든 줄에서 잘려나가는 그림">
<rect x="150" y="24" width="12" height="176" fill="var(--accent-soft)"></rect>
<line x1="156" y1="18" x2="156" y2="212" stroke="var(--accent)" stroke-width="1.6" stroke-dasharray="4 4"></line>
<text x="156" y="234" text-anchor="middle" font-size="13" fill="var(--accent)">기준선</text>
<g font-family="var(--font-mono)" font-size="15" fill="var(--text)">
<text x="30" y="44">string s = """</text>
<text x="162" y="80">{</text>
<text x="186" y="110">"name": "John"</text>
<text x="162" y="140">}</text>
<text x="120" y="176">""";</text>
</g>
<g font-size="13" fill="var(--text-muted)">
<text x="400" y="80">값의 첫 줄</text>
<text x="400" y="110">앞의 두 칸은 남습니다</text>
<text x="400" y="140">값의 마지막 줄</text>
<text x="400" y="176">이 칸이 기준입니다</text>
</g>
<g stroke="var(--border)" stroke-width="1.2" fill="none">
<path d="M320 76 L392 76"></path>
<path d="M330 106 L392 106"></path>
<path d="M320 136 L392 136"></path>
<path d="M180 172 L392 172"></path>
</g>
</svg>
<figcaption>닫는 따옴표보다 왼쪽에 있는 공백만 잘립니다. 그보다 안쪽의 들여쓰기는 값에 남습니다.</figcaption>
</figure>

닫는 따옴표보다 **왼쪽으로 튀어나온 줄이 있으면 컴파일 에러**입니다.
잘라낼 수 없는 줄이 있다는 뜻이라 컴파일러가 거부합니다.

**3. 안에 따옴표 세 개가 필요하면 밖을 네 개로 늘립니다.**

여는 따옴표 개수보다 적게 이어진 따옴표는 전부 그냥 글자입니다.

```csharp
string s = """"
    이 안에서는 """ 도 그냥 글자입니다.
    """";
```

### 보간과 같이 쓰기

앞 글의 `$` 를 그대로 붙일 수 있습니다.

```csharp
string name = "손오공";

string s = $"""
    이름: {name}
    """;
```

문제는 **내용에 중괄호가 들어 있을 때**입니다. JSON 이 정확히 그렇습니다.
이때는 `$` 를 두 개 쓰면 **중괄호 두 개짜리만 값으로 바뀝니다.**

```csharp
string json = $$"""
    {
      "name": "{{name}}",
      "age": 30
    }
    """;
```

`{` 하나는 글자로 남고 `{{name}}` 만 값으로 바뀝니다.
`$` 개수가 곧 "값으로 바꿀 중괄호 개수" 입니다.

## 무엇을 쓸까

| 상황 | 쓸 것 |
| --- | --- |
| 한 줄짜리 짧은 문자열 | 그냥 따옴표 |
| 줄바꿈이나 탭이 필요할 때 | `\n`, `\t` |
| 윈도우 파일 경로 | `@` 또는 따옴표 세 개 |
| JSON, XML, SQL, 여러 줄 텍스트 | 따옴표 세 개 |
| 다른 데서 복사해 온 텍스트 | 따옴표 세 개 |

기준은 단순합니다. **원본이 있고 그 모양이 유지돼야 하면 따옴표 세 개**입니다.
붙여넣고 손대지 않아도 되니 옮겨 적다가 생기는 실수가 없습니다.

> 따옴표 세 개는 C# 11 부터입니다. `dotnet --version` 이 7.0 이상이면 쓸 수 있습니다.
> 유니티는 버전에 따라 지원하는 C# 버전이 다르므로, 안 되면 `@` 를 쓰면 됩니다.

## 정리

- 따옴표와 역슬래시는 문법에 쓰이는 기호라서 문자열 안에 그냥 쓸 수 없다.
- 역슬래시를 앞에 붙이면 "이건 문법이 아니라 글자다" 라는 표시가 된다.
- 역슬래시와 그다음 글자는 합쳐서 문자 하나다. `"A\nB".Length` 는 3이다.
- `@` 는 역슬래시만 풀어준다. 따옴표는 여전히 두 번 써야 한다.
- 따옴표 세 개 안에서는 적은 그대로가 값이다. 역슬래시도 따옴표도 특별하지 않다.
- 닫는 따옴표의 들여쓰기가 기준선이고, 그 왼쪽 공백은 모든 줄에서 잘린다.
- 내용에 중괄호가 있으면 `$$` 로 열고 `{{ }}` 로 값을 넣는다.