1. 도입
프로그램 안의 객체를 멋진 복잡한 LEGO 조립품이라고 상상해보세요. 여러 부품이 각각 제자리에 있고, 전체가 하나의 완성품을 이루고 있죠. 그 조립품이 책상 위(컴퓨터의 메모리)에 있는 동안에는 아무 문제없습니다. 그런데 이걸 친구 집으로 옮기거나(네트워크로 전송), 다음번까지 박스에 넣어 보관해야 한다면 어떻게 할까요? 그냥 통째로 평평한 상자에 넣으면 부서지겠죠!
바로 이걸 안전하게 보관하거나 전송하기 위해 객체를 조심스럽게 분해하고 다시 조립할 수 있게 만드는 과정이 바로 직렬화입니다. 객체를 메모리의 "살아있는" 상태에서 파일에 저장하거나 네트워크로 전송할 수 있는 바이트 시퀀스(또는 텍스트 표현)로 변환하는 과정이에요. 쉽게 말하면 LEGO 조립품을 부품별로 정리하고 봉투에 넣고 라벨을 붙여서 박스에 포장하는 것과 같아요.
flowchart LR
ObjectInMemory(메모리의 객체)
Serialize[직렬화]
BytesOrText[바이트 / 텍스트 파일 / JSON / XML]
Deserialize[역직렬화]
ObjectAgain(다시 메모리의 객체)
ObjectInMemory -->|serialize| Serialize
Serialize --> BytesOrText
BytesOrText -->|deserialize| Deserialize
Deserialize --> ObjectAgain
그리고 당연히 포장만 한 뒤에는 다시 잘 풀 줄 알아야겠죠! 반대 과정은 역직렬화라고 합니다. 박스에서 봉투들을 꺼내서(혹은 파일에서 바이트를 읽어서), 설명서대로(또는 어떻게 조립했는지 기억을 더듬어) 원래 상태와 똑같이 메모리 안에 객체를 복원하는 과정이에요. 마치 마법 같죠!
"직렬화"라는 용어는 문자 그대로 "순차적 표현"을 의미해요. 메모리 안에서 복잡하게 서로를 참조하는 구조(객체 그래프)를 하나의 선형적이고 순차적인 형태로 바꿔서 파일에 쓰거나 전송하기 쉽도록 만드는 거예요.
2. 왜 단순히 File.WriteAllText만으로는 부족할까?
좋은 질문이에요! 모든 데이터가 단순한 문자열이나 숫자였다면 직렬화가 필요 없겠죠. 하지만 실제로 애플리케이션은 복잡한 모델들과 함께 동작합니다: 고객, 주문, 상품, 학생, 캐릭터, 레벨 등등. 이들은 모두 메모리 안의 객체들이에요. 그래서 직렬화가 중요한 이유는 다음과 같아요:
애플리케이션 상태 저장
텍스트 편집기를 만든다고 가정해볼게요. 사용자가 텍스트를 입력하고 폰트를 바꾸고 이미지를 삽입하죠. 이런 데이터들(텍스트, 설정, 커서 위치)은 결국 프로그램의 객체들입니다. 사용자가 "저장"을 누르면 다음 실행 시에도 그대로 보이길 원하잖아요? 직렬화는 필요한 객체들을 "얼려서" 디스크에 저장하고, 다음에 로드할 때 "녹여서" 같은 상태로 복원하게 해줍니다. 게임의 "저장" 기능과 같아요. 정전 났을 때 레벨을 다시 깨고 싶지 않잖아요?
애플리케이션 간 데이터 교환
C#으로 만든 프로그램이 Python으로 만든 웹 서버와 통신한다고 해봅시다. 또는 모바일 앱(Xamarin이나 MAUI 같은)과 .NET 서버가 통신할 수도 있고요. 이 프로그램들은 메모리를 공유하지 않으니까 데이터를 주고받으려면 공통된 형식이 필요해요. 직렬화는 객체를 그 공통 형식(예: JSON이나 XML)으로 바꿔서 네트워크로 보낼 수 있게 합니다. 반대편에서는 그 데이터를 역직렬화해서 자신의 언어에 맞는 객체로 되돌리죠. 직렬화가 없다면 매번 손으로 데이터를 풀고 재조립해야 해서 엄청 번거롭고 오류가 많습니다!
마치 국제적으로 물건을 보낼 때 규격화된 컨테이너에 포장하는 것과 같아요. 상대방이 그 컨테이너를 열면 원래 물건을 꺼낼 수 있죠(역직렬화).
앱 설정
앱에는 창 크기, 파일 경로, 최근에 연 문서, 컬러 테마 같은 설정이 많아요. 각 설정을 변수로 관리하고 하나하나 파일로 저장하는 건 비효율적이고 번거롭죠. 차라리 앱설정 같은 클래스를 만들어서 필요한 설정을 속성으로 두고, 그 객체 하나를 통째로 직렬화해서 파일에 저장하면 간편합니다. 실행 시에는 역직렬화하면 되고요. 편하죠?
캐싱
데이터베이스나 원격 서버에서 데이터를 가져오는 데 시간이 오래 걸릴 수 있어요. 매번 요청하지 않으려면 한 번 받아온 데이터를 직렬화해서 캐시에 저장해두면 됩니다(디스크나 전용 저장소). 다음 요청 때 캐시가 있으면 역직렬화해서 바로 쓰면 되니 속도가 훨씬 빨라지고 외부 리소스 부담도 줄어듭니다.
3. 핵심 아이디어: "상태" 저장
직렬화에서 가장 중요한 개념은 바로 상태 저장입니다. 객체는 그 순간의 필드들과 값들의 집합이에요. 직렬화는 그 상태를 "사진 찍듯" 기록합니다. 역직렬화할 때는 단순히 빈 객체를 새로 만드는 것이 아니라, 직렬화 시점의 모든 필드 값들을 갖춘 상태로 복원하는 거예요.
단순한 데이터 복사 이상이에요. 구조와 값들을 깊게 복사하고, 참조하는 다른 객체들까지 포함할 수 있습니다(직렬화기가 그걸 지원하면요). 어떤 직렬화기는 객체의 타입 정보를 함께 저장해서, 역직렬화 시점에 정확한 클래스로 복원할 수 있게 해주기도 합니다. 심지어 역직렬화 시점에 정확한 타입을 모를 때도요.
클래식한 예: 반려동물 직렬화
우리의 "반려동물 백과" 앱을 계속 발전시킨다고 해봅시다. 예를 들어 다음과 같은 클래스가 있어요:
public class Pet
{
public string Name { get; set; }
public string Type { get; set; } // 예: "고양이", "개"
public int Age { get; set; }
}
우리가 하고 싶은 것은:
- 메모리 안에 이런 객체들의 컬렉션을 채우고,
- 그걸 파일에 저장하고,
- 나중에(예: 프로그램 재시작 후) 복원하는 것
개략적으로는 이렇게 됩니다:
컬렉션 List<Pet>
↓ 직렬화
파일 (JSON, XML, 바이트)
↓ 역직렬화
컬렉션 List<Pet> (다시 메모리에!)
4. 직렬화 포맷들
운송업에도 골판지 상자, 목재 상자, 금속 컨테이너 등 다양한 포장이 있듯이, 프로그래밍에도 여러 가지 직렬화 포맷이 있어요. 각 포맷은 장단점과 특징이 있습니다.
여기서 깊게 들어가진 않겠지만, 다음 강의들에서 계속 나오니까 몇 가지 이름만 기억해두세요:
- JSON (JavaScript Object Notation): 아마 지금 가장 인기 있는 포맷일 거예요. 텍스트 기반이고(비교적) 사람이 읽기 쉽고, 웹에서 데이터 교환에 널리 쓰입니다. "키-값" 쌍으로 중괄호에 감싸진 형태예요. 본질적으로 구조화된 텍스트라서 프로그램들이 파싱하기 쉬워요.
- XML (Extensible Markup Language): 더 오래된 텍스트 포맷인데 여전히 많이 쓰입니다. HTML처럼 태그 기반이고 구조가 엄격해요. 기업용 시스템의 설정 파일이나 데이터 교환에 자주 쓰이며, 사람이 읽을 수 있지만 종종 좀 장황합니다.
- 바이너리 포맷: 데이터를 텍스트 대신 메모리에서처럼 바이트로 직접 저장하는 포맷들입니다. 보통 더 작고 직렬화/역직렬화가 빠르지만 사람은 읽을 수 없어요. 성능이나 파일 크기가 중요한 경우(예: 게임 저장 데이터나 대용량 내부 컴포넌트 간 전송)에 주로 사용됩니다.
어떤 포맷을 쓸지는 상황에 달려요: 사람이 쉽게 읽을 수 있어야 하나요? 속도와 파일 크기가 중요한가요? 어떤 플랫폼으로 데이터를 보낼 건가요? 서로 다른 시스템 간의 데이터 교환에는 보편성 때문에 JSON이나 XML이 자주 선호됩니다. 같은 애플리케이션 내부 저장용이라면 바이너리 포맷이 더 빠르고 효율적일 수 있어요.
보시다시피, 직렬화는 많은 현대 애플리케이션의 기반이 되는 강력한 도구예요. 프로그램이 실행 간 상태를 기억하게 하고, 서로 소통하게 하며, 복잡한 정보를 효율적으로 관리하게 해줍니다. 다음 강의부터는 이걸 그냥 "마법"으로 두지 않고 .NET 라이브러리들을 직접 사용해보면서 실습할 거예요. 기대하세요 — 재밌을 거예요!
GO TO FULL VERSION