
C#에서 class를 만들 때, 생성자와 속성(Property)들을 일일이 타이핑하곤 하죠.
그런데 "데이터 묶음이 필요할 뿐인데, 코드가 많지?"라고 생각해 본 적이 있나요?
C# 9.0부터 이런 고민을 한 방에 해결해 줄 레코드(Record)라는 기능이 도입되었습니다.
이번 글에서는 레코드가 무엇인지, 클래스와는 어떻게 다른지 알아보겠습니다!
레코드는 데이터를 캡슐화하기 위한 특별한 목적의 클래스(또는 구조체)입니다.
순수하게 데이터를 담는 용도의 객체(DTO, Data Transfer Object)로 사용됩니다.
핵심 철학은 "한 번 생성된 데이터는 변하지 않는다"라는 불변성(Immutability)에 있습니다.
먼저 기존 class로 데이터를 표현할 때의 모습을 봅시다.
이 클래스의 목적이 단지 '이름'과 '나이'라는 데이터를 담아두는 것이라면,
클래스로 만들 때 해야 할 일이 은근히 많습니다.
// 레코드가 없던 시절...
public class PersonClass
{
public string Name { get; set; }
public int Age { get; set; }
public PersonClass(string name, int age)
{
Name = name;
Age = age;
}
// 데이터를 비교하려면 Equals, GetHashCode 등을 직접 재정의해야 함...
// 객체 정보를 예쁘게 출력하려면 ToString을 직접 재정의해야 함...
// ... 등등
}
단순히 데이터를 묶어두고 싶었을 뿐인데, 객체를 제대로 비교하고
출력하려면 이런 번거로운 코드(Boilerplate code)를 잔뜩 작성해야 했습니다.
똑같은 기능을 하는 Person을 레코드로 정의하면, 단 한 줄이면 충분합니다.
public record PersonRecord(string Name, int Age);
record키워드 하나만으로 컴파일러는 다음과 같은 기능들을 자동으로 만들어줍니다.
레코드의 속성(Name, Age)은 내부적으로 init접근자를 사용합니다.
즉, 객체를 생성할 때 딱 한 번만 값을 할당할 수 있고, 그 이후에는 변경할 수 없습니다.
var person = new PersonRecord("김철수", 30);
// person.Age = 31; // 컴파일 오류! init-only 속성은 변경할 수 없습니다.
레코드의 가장 강력한 특징 중 하나입니다!
true입니다. (참조 비교)true입니다. (값 비교)[코드]
public record PersonRecord(string Name, int Age);
public class PersonClass
{
public string Name { get; set; }
public int Age { get; set; }
public PersonClass(string name, int age)
{
Name = name;
Age = age;
}
}
class Program
{
static void Main()
{
// 클래스 비교
var class1 = new PersonClass("홍길동", 25);
var class2 = new PersonClass("홍길동", 25);
Console.WriteLine($"클래스 동등성 비교: {class1 == class2}");
// 레코드 비교
var record1 = new PersonRecord("홍길동", 25);
var record2 = new PersonRecord("홍길동", 25);
Console.WriteLine($"레코드 동등성 비교: {record1 == record2}");
}
}
[실행 결과]
클래스 동등성 비교: False
레코드 동등성 비교: True
디버깅할 때 Namespace.ClassName같은 의미 없는 문자열을 보지 않아도 됩니다.
[코드]
public record PersonRecord(string Name, int Age);
class Program
{
static void Main()
{
var person = new PersonRecord("홍길동", 25);
Console.WriteLine(person.ToString());
}
}
[실행 결과]
PersonRecord { Name = 홍길동, Age = 25 }
"불변이라면서요? 그럼 나이를 한 살 올리고 싶을 땐 어떡하죠?"
이럴 때 사용하는 것이 바로 with표현식입니다. 원본 객체는 그대로 둔 채,
특정 속성만 변경된 새로운 복사본 객체를 아주 간단하게 만들 수 있습니다.
[코드]
public record PersonRecord(string Name, int Age);
class Program
{
static void Main()
{
var person1 = new PersonRecord("이영희", 20);
// 'person1'을 기반으로 Age만 21로 바꾼 '새로운' 레코드를 생성
var person2 = person1 with { Age = 21 };
Console.WriteLine(person1); // (원본은 불변!)
Console.WriteLine(person2); // (새로운 복사본)
}
}
[실행 결과]
PersonRecord { Name = 이영희, Age = 20 }
PersonRecord { Name = 이영희, Age = 21 }
레코드와 클래스는 사용 목적이 다릅니다.
| 구분 | 레코드(record) | 클래스(class) |
|---|---|---|
| 핵심 목적 | 데이터 묶음 (What it is) | 행위와 상태를 가진 객체 (What it does) |
| 불변성 | 불변(Immutable)을 지향 | 가변(Mutable)이 기본 |
| 동등성 비교 | 값(Value) 기반 | 참조(Reference) 기반 |
| 주 사용처 | DTO, API 모델, 읽기 전용 데이터 구조 | 서비스, 로직 처리, 상태가 계속 변하는 객체 |
[간단한 기준]
"이 객체는 데이터 그 자체가 중요해!" → 레코드
"이 객체는 고유한 정체성을 갖고 상태가 변하며 일을 해야 해!" → 클래스
C# 레코드는 데이터를 다루기 위한 현대적이고 강력한 방법입니다.
ToString(), with표현식 등을 자동으로 제공합니다.