Deconstruction — 튜플이나 다른 형식에서 속성·필드 꺼내기
Deconstruction — 튜플이나 다른 형식에서 속성·필드 꺼내기
*디스트럭처링 표현식(deconstruction expression)*은 객체 인스턴스에서 데이터 필드를 꺼내는 표현식이에요. 꺼낸 각 데이터 요소는 서로 다른 변수에 하나씩 담기죠. 다음 예시를 볼게요.
var tuple = (X: 1, Y: 2);
var (x, y) = tuple;
Console.WriteLine(x); // output: 1
Console.WriteLine(y); // output: 2
위 코드는 정수 값 X, Y 두 개를 가진 튜플을 만들고 있어요. 두 번째 문장에서 그 튜플을 디스트럭처링해서, 튜플의 요소를 별도의 변수 x와 y에 나눠 담는 거죠.
이 기능은 튜플에 처음 도입됐어요. 이후 레코드, 클래스, 구조체, 인터페이스까지 확장됐는데, 지금부터 하나씩 살펴볼게요.
본문
튜플 디스트럭처링
모든 튜플 형식이 디스트럭처링 표현식을 지원해요. 튜플 디스트럭처링은 튜플의 모든 요소를 꺼내요. 요소 일부만 필요하다면 쓰지 않는 튜플 멤버 자리에 discard를 쓰면 돼요. 다음 예시를 볼게요.
var tuple2 = (X: 0, Y: 1, Label: "The origin");
var (x2, _, _) = tuple2;
위 예시에서는 Y와 label 멤버를 버리고 있어요. 같은 디스트럭처링 표현식 안에 discard를 여러 개 둘 수도 있고, 튜플의 모든 멤버에 discard를 쓸 수도 있어요. 다음 예시는 별로 쓸모는 없지만 문법상 맞아요.
var (_, _, _) = tuple2;
레코드 디스트럭처링
기본 생성자를 가진 레코드 형식은 위치 매개변수(p positional parameter)에 대한 디스트럭처링을 지원해요. 컴파일러가 Deconstruct 메서드를 합성해 주는데, 이 메서드는 기본 생성자의 위치 매개변수에서 합성된 속성들을 꺼내 줘요. 컴파일러가 합성하는 Deconstruct 메서드는 레코드 형식에서 속성으로 선언한 것들은 꺼내지 않아요.
다음 코드의 record는 위치 속성 SquareFeet, Address 두 개와 별도의 속성 RealtorNotes 하나를 선언하고 있어요.
public record House(int SquareFeet, string Address)
{
public required string RealtorNotes { get; set; }
}
House 객체를 디스트럭처링하면 위치 속성들만, 그리고 위치 속성 모두가 꺼져요. 다음 예시를 볼게요.
var house = new House(1000, "123 Coder St.")
{
RealtorNotes = """
This is a great starter home, with a separate room that's a great home office setup.
"""
};
var (squareFeet, address) = house;
Console.WriteLine(squareFeet); // output: 1000
Console.WriteLine(address); // output: 123 Coder St.
Console.WriteLine(house.RealtorNotes);
이 동작을 활용하면 레코드 형식의 속성 중 어떤 것이 컴파일러가 합성하는 Deconstruct 메서드에 포함될지를 직접 정할 수 있어요.
Deconstruct 메서드 선언하기
직접 선언한 클래스·구조체·인터페이스 어디에나 디스트럭처링 지원을 추가할 수 있어요. 형식 안에 Deconstruct 메서드를 하나 이상 선언하거나, 그 형식에 대한 확장 메서드로 선언하면 돼요. 디스트럭처링 표현식은 void Deconstruct(out var p1, ..., out var pn) 형태의 메서드를 호출해요. Deconstruct 메서드는 인스턴스 메서드가 될 수도, 확장 메서드가 될 수도 있어요. Deconstruct 메서드의 각 매개변수 형식은 디스트럭처링 표현식에서 대응하는 인자의 형식과 일치해야 해요. 디스트럭처링 표현식은 각 인자의 값을 Deconstruct 메서드의 해당 out 매개변수 값에 할당하는 거죠. 디스트럭처링 표현식과 일치하는 Deconstruct 메서드가 여러 개라면, 컴파일러가 모호성(ambiguity) 오류를 보고해요.
다음 코드는 Deconstruct 메서드 두 개를 가진 Point3D 구조체를 선언해요.
public struct Point3D
{
public int X { get; set; }
public int Y { get; set; }
public int Z { get; set; }
public void Deconstruct(out int x, out int y, out int z)
{
x = X;
y = Y;
z = Z;
}
public void Deconstruct(out int x, out int y)
{
x = X;
y = Y;
}
}
첫 번째 메서드는 세 축 값 X, Y, Z를 모두 꺼내는 디스트럭처링 표현식을 지원해요. 두 번째 메서드는 평면 값 X, Y만 디스트럭처링하는 표현식을 지원하죠. 첫 번째 메서드는 *인자 수(arity)*가 3이고, 두 번째는 2예요.
앞에서 기본 생성자를 가진 record 형식에 대해 컴파일러가 합성하는 Deconstruct 메서드를 설명했는데요, 레코드 형식에 Deconstruct 메서드를 더 선언할 수도 있어요. 이런 메서드는 다른 속성을 추가하거나, 기본 속성 일부를 제거하거나, 둘 다 할 수 있어요. 컴파일러가 합성한 것과 같은 시그니처의 Deconstruct를 직접 선언할 수도 있는데, 그렇게 선언하면 컴파일러는 자체적으로 합성하지 않아요.
Deconstruct 메서드가 여러 개여도, 컴파일러가 디스트럭처링 표현식에 대해 유일한 Deconstruct 메서드 하나를 정확히 정할 수 있다면 괜찮아요. 보통 같은 형식의 Deconstruct 메서드 여러 개는 매개변수 개수가 달라요. 매개변수 형식이 다른 Deconstruct 메서드를 여러 개 만들 수도 있고요. 다만 많은 경우 Deconstruct 메서드가 너무 많으면 모호성 오류와 오해를 부르는 결과로 이어질 수 있어요.
var point = new Point3D { X = 1, Y = 2, Z = 3 };
// Deconstruct 3D coords
var (x3, y3, z3) = point;
Console.WriteLine(x3); // output: 1
Console.WriteLine(y3); // output: 2
Console.WriteLine(z3); // output: 3
// Deconstruct 2D coords
var (x4, y4) = point;
Console.WriteLine(x4); // output: 1
Console.WriteLine(y4); // output: 2
아까 Point3D에서 봤듯이, 같은 인스턴스를 세 축으로 디스트럭처링할 수도, 평면 값으로 디스트럭처링할 수도 있어요. 컴파일러가 인자 개수로 어느 Deconstruct를 쓸지 정해 주는 거죠.
C# 언어 사양
더 자세한 내용은 C# 표준의 디스트럭처링 섹션을 참고하세요.