빠른 흐름
// 경로 조합 (OS 구분자 자동 처리)
string path = Path.Combine("data", "memo.txt");
// 소규모 파일 — File 정적 메서드
File.WriteAllText(path, "안녕하세요", Encoding.UTF8);
string content = File.ReadAllText(path, Encoding.UTF8);
string[] lines = File.ReadAllLines(path, Encoding.UTF8);
// 대용량 파일 — async + StreamReader
await using var reader = new StreamReader(path, Encoding.UTF8);
while (!reader.EndOfStream)
{
string? line = await reader.ReadLineAsync();
// 한 줄씩 처리 — 전체를 메모리에 올리지 않음
}기본 흐름
어떤 파일 IO 형태를 먼저 고르면 되나
파일 카드는 아래 네 경우를 먼저 나누면 대부분의 코드가 정리됩니다.
string text = File.ReadAllText(path, Encoding.UTF8); // 작은 텍스트 파일
await File.WriteAllTextAsync(path, text, Encoding.UTF8); // 작은 파일 async 쓰기
await using var reader = new StreamReader(path); // 큰 파일 스트리밍
byte[] bytes = File.ReadAllBytes(path); // 바이너리 파일- 작은 텍스트 파일:
File.*AllText - 큰 파일:
StreamReader/StreamWriter - 바이너리:
ReadAllBytes,FileStream - 비동기 I/O:
*Async
File 정적 메서드 — 내부에서 Stream을 열고 닫는다
File.ReadAllText()는 편의 메서드입니다. 내부적으로 FileStream을 열고, 모든 바이트를 읽어 문자열로 변환한 뒤, 스트림을 닫습니다. 작은 파일에는 간결하지만, 파일 전체를 메모리에 한 번에 올리기 때문에 수백 MB 파일에는 적합하지 않습니다.
// 이 한 줄이 내부에서 하는 일
string text = File.ReadAllText("data.txt");
// 실제로는 아래와 동일
using var fs = new FileStream("data.txt", FileMode.Open, FileAccess.Read);
using var sr = new StreamReader(fs, Encoding.UTF8);
string text = await sr.ReadToEndAsync();async 파일 IO — UI/서버 스레드를 블록하지 않는다
File.ReadAllTextAsync() (C# 6+)와 StreamReader.ReadLineAsync()는 OS의 비동기 I/O를 사용합니다. 파일을 기다리는 동안 스레드가 다른 작업을 할 수 있어 서버 처리량이 올라갑니다.
// 소규모 async 읽기
string json = await File.ReadAllTextAsync("config.json", Encoding.UTF8);
// 대용량 줄 단위 async 읽기
await using var reader = new StreamReader("big-log.txt");
await foreach (string? line in reader.ReadAllLinesAsync()) // .NET 9+
Process(line);
// .NET 9 이전 — while 루프
string? line;
while ((line = await reader.ReadLineAsync()) != null)
Process(line);Path 유틸리티 — 경로 문자열을 직접 다루지 않는다
운영체제마다 경로 구분자가 다릅니다 (Windows \, Linux/macOS /). Path 클래스는 이 차이를 추상화합니다.
string dir = Path.GetDirectoryName("data/logs/app.log"); // "data/logs"
string file = Path.GetFileName("data/logs/app.log"); // "app.log"
string ext = Path.GetExtension("app.log"); // ".log"
string noExt = Path.GetFileNameWithoutExtension("app.log"); // "app"
// 절대 경로 조합
string full = Path.GetFullPath(Path.Combine("data", "output.txt"));
// 임시 파일
string tmp = Path.GetTempFileName(); // 실제로 파일 생성됨 — 사용 후 삭제 필요인코딩 — 기본값의 함정
File.ReadAllText(path)처럼 인코딩을 생략하면 UTF-8 (BOM 감지)를 사용합니다. BOM이 없는 UTF-8 파일은 대부분 올바르게 읽히지만, EUC-KR 같은 다른 인코딩은 깨집니다. 인코딩을 명시하는 것이 안전합니다.
// UTF-8 (BOM 없음) — 명시적 지정
var utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
string text = File.ReadAllText(path, utf8);
// EUC-KR 파일 읽기
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // .NET 5+
var eucKr = Encoding.GetEncoding("EUC-KR");
string text = File.ReadAllText(path, eucKr);Directory 작업
// 디렉터리 생성 (중간 경로 포함)
Directory.CreateDirectory("data/logs/2026");
// 파일 목록
string[] txts = Directory.GetFiles("data", "*.txt", SearchOption.AllDirectories);
// 존재 확인
bool exists = File.Exists(path);
bool dirExists = Directory.Exists("data/logs");// ❌ 경로를 문자열로 직접 이어 붙이면 OS마다 깨질 수 있다
string wrong = "data/" + fileName;
// ✅ Path.Combine 사용
string safe = Path.Combine("data", fileName);체크포인트
| 상황 | 추천 방법 |
|---|---|
| 소규모 파일 읽기 | File.ReadAllText() / ReadAllTextAsync() |
| 대용량 파일 (줄 단위) | StreamReader + ReadLineAsync() |
| 파일 쓰기 (추가) | File.AppendAllText() / StreamWriter(append: true) |
| 바이너리 파일 | File.ReadAllBytes() / FileStream |
| 경로 조합 | Path.Combine() — 직접 문자열 연결 금지 |
| 인코딩 | 항상 명시적으로 지정 |
주의할 점
StreamReader와 StreamWriter는 IDisposable입니다. using 또는 await using으로 반드시 닫아야 합니다. 닫지 않으면 파일 핸들이 유지되어 다른 프로세스나 같은 프로세스의 다른 코드가 파일을 열 수 없게 됩니다.
File.WriteAllText()는 파일이 없으면 생성하고, 있으면 덮어씁니다. 기존 내용을 보존하면서 추가하려면 File.AppendAllText()를 사용하세요. 대규모 로그처럼 자주 추가하는 경우에는 StreamWriter(append: true)가 핸들을 열어두므로 더 효율적입니다.
참고 링크
3 sources