Favicon

Record type

Peponi11/20/20248m

C#
SyntaxTyperecord

1. Introduction

record 형식은 C# 9 (.NET 5) 에서 소개된 Immutable reference type을 목적으로 설계된 형식이다. 기본적으로 class 형식과 비슷하며 형식 사용 방법 또한 상당히 유사하다. 다른 점으로는 immutable 지원을 위한 기능이 추가되어 최소한의 코드로 immutable reference type을 구현할 수 있다. C# 10 (.NET 6) 에서 record struct가 소개되면서 값 형식의 사용이 가능해지고, 명시적으로 record class 선언을 할 수 있게 되었다.

2. Record의 장점

record 형식은 다음과 같은 장점을 가진다.

  1. Immutable reference type
  2. Constructor & Deconstruct 자동 지원
  3. with식 지원
  4. Equals(object), == & != 지원
    • 이 부분은 class와 다른 부분이다. 형식 및 값, 참조 검사가 지원된다.
  5. PrintMembers & ToString() 자동 지원
  6. class와 같이 상속을 지원한다.

3. Record 정의

3.1. Record 기본 정의

record 또는 record class 형식으로 정의한다.

public record CartesianCoordinate
{
    public double X { get; set; }
    public double Y { get; set; }
}

상기 정의에서는 property setter 키워드를 일부러 set으로 설정하였다. 이런 경우 아래와 같이 프로퍼티 값을 변경하는 것이 가능하다.

var position = new CartesianCoordinate{
    X = 1,
    Y = 2
};
 
position.X = 5;

상기와 같이 정의를 하고 사용하는 경우 class와 차이가 없다. (일부러 record를 사용할 필요가 없다) 따라서 immutable을 위해 다음과 같이 정의하도록 한다.

3.2. Immutable record 정의

public record CartesianCoordinate
{
    public double X { get; init; }      // init 키워드 : C# 9
    public double Y { get; init; }
}

초기화 시에만 값을 할당하게 해주는 init 키워드를 통해 record가 불변성을 갖게 되었다. 다음 절을 통해, record의 구체적인 정의 및 사용법을 알아본다.

4. Positional record

4.1. Positional record, class 정의

Positional record는 다음과 같이 메서드와 비슷한 형태로 정의한다.

public record CartesianCoordinate(double X, double Y);  // X, Y에 대해 컴파일러가 프로퍼티 (get; init;) 를 구성해준다.

이와 유사한 기능을 할 수 있는 class를 구현하면 아래와 같이 구현된다.

public class CartesianCoordinate
{
    public double X { get; init; }
    public double Y { get; init; }
 
    public CartesianCoordinate(double x, double y)
    {
        X = x;
        Y = y;
    }
 
    public void Deconstruct(out double x, out double y)
    {
        x = X;
        y = Y;
    }
 
    public override string ToString()
    {
        StringBuilder builder = new StringBuilder();
        builder.Append(nameof(CartesianCoordinate));
        builder.Append(" { ");
 
        if (PrintMembers(builder))
        {
            builder.Append(" ");
        }
 
        builder.Append("}");
 
        return builder.ToString();
    }
 
    protected virtual bool PrintMembers(StringBuilder builder)
    {
        builder.Append($"X = {X}, ");
        builder.Append($"Y = {Y}");
        return true;
    }
}

4.2. Positional record, class 비교

4.1. Positional record, class 정의 내용을 통해 우리는 컴파일러가 record, 특히 positional record를 위해 생성해주는 코드를 알 수 있다.

  1. 파라미터에 맞는 프로퍼티를 생성한다. (Public 속성이다)
  2. 파라미터에 맞는 생성자를 만든다.
  3. 파라미터에 맞는 Deconstruct() 메서드를 정의해준다.
  4. record명과 프로퍼티를 적절하게 출력할 수 있는 ToString()을 정의해준다.

이는 우리가 작성해야 할 코드의 양이 획기적으로 줄어드는 것을 의미한다. 특히 상속이 이루어지는 경우, positional record의 효율성은 극대화된다.

public record CartesianCoordinate(double X, double Y);
public record CartesianCoordinate3D(double X, double Y, double Z) : CartesianCoordinate(X, Y);

따라서 불변성을 가지는 참조 형식의 데이터가 필요한 경우 positional record가 좋은 선택지가 될 수 있다.

4.3. Positional parameter 재정의 및 프로퍼티 생성

Positional record의 자동 구현 프로퍼티가 마음에 들지 않는 경우 같은 이름으로 프로퍼티를 정의할 수 있다. 또한 positional parameter 외에 추가로 프로퍼티를 생성할 수도 있다.

public record CartesianCoordinate(double X)
{
    internal double X { get; init; } = X;   // 같은 이름의 프로퍼티 정의
    public double Y { get; init; } = 0;     // 프로퍼티 생성
}

5. Positional record 사용

Positional record는 다음과 같이 사용한다.

public record CartesianCoordinate(double X, double Y);
 
CartesianCoordinate coordinate = new(1, 2);
record 출력
Console.WriteLine(coordinate.X);    // 1
Console.WriteLine(coordinate.Y);    // 2
Console.WriteLine(coordinate);      // CartesianCoordinate { X = 1, Y = 2 }
Deconstruct
var (X, Y) = coordinate;    // X = 1, Y = 2
with 식 사용
CartesianCoordinate coordinate1 = coordinate with { X = 3 };
Console.WriteLine(coordinate1);     // CartesianCoordinate { X = 3, Y = 2 }
객체 비교
coordinate1 = coordinate with {};
 
Console.WriteLine(coordinate == coordinate1);   // True
Console.WriteLine(object.ReferenceEquals(coordinate, coordinate1));     // False

6. Record struct

C# 10에서 record struct 타입이 새로 추가되었다. 기존의 record (record class) 는 참조 형식, record struct 형식은 값 형식이라는 차이가 있다. Positional record 선언 시 record class는 프로퍼티가 { get; init; }의 형태로 생성되지만 record struct{ get; set; }으로 생성되어 값의 수정이 가능하다.

public record CartesianCoordinate(float X, float Y);
public record struct CartesianCoordinate(float X, float Y);
record class
public record CartesianCoordinate
{
    public float X { get; init; }
    public float Y { get; init; }
}
record struct
public record struct CartesianCoordinate
{
    public float X { get; set; }
    public float Y { get; set; }
}

WARNING

불변성을 지닌 record를 정의하더라도, 주의해야 할 사항이 있다.

  • 파라미터에 대한 완전한 불변성은 값 형식에 대해서만 한정된다.
  • 참조 형식 파라미터의 경우, 참조에 대해서만 불변성이 보장되고 데이터는 변경될 수 있다.
public record CargoList(string ContainerName, List<string> Items);
 
CargoList cargo = new("My Container", new List<string>() { "Stone", "Fish" });
 
Console.WriteLine(cargo);              // CargoList { ContainerName = My Container, Items = System.Collections.Generic.List`1[System.String] }
Console.WriteLine(cargo.Items[0]);     // Stone
 
cargo.Items[0] = "TV";
 
Console.WriteLine(cargo.Items[0]);     // TV

7. 참조 자료