Record type
Peponi │ 11/20/2024 │ 8m
Record type
Peponi
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
형식은 다음과 같은 장점을 가진다.
Immutable reference type
- Constructor & Deconstruct 자동 지원
- Positional record 구현 시
- with식 지원
Equals(object)
,==
&!=
지원- 이 부분은
class
와 다른 부분이다. 형식 및 값, 참조 검사가 지원된다.
- 이 부분은
- PrintMembers &
ToString()
자동 지원 - 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
를 위해 생성해주는 코드를 알 수 있다.
- 파라미터에 맞는 프로퍼티를 생성한다. (Public 속성이다)
- 파라미터에 맞는 생성자를 만든다.
- 파라미터에 맞는
Deconstruct()
메서드를 정의해준다. 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);
Console.WriteLine(coordinate.X); // 1
Console.WriteLine(coordinate.Y); // 2
Console.WriteLine(coordinate); // CartesianCoordinate { X = 1, Y = 2 }
var (X, Y) = coordinate; // X = 1, Y = 2
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);
public record CartesianCoordinate
{
public float X { get; init; }
public float Y { get; init; }
}
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