레코드(C# 참조)

record 한정자를 사용하여 데이터를 캡슐화하는 내장된 기능을 제공하는 참조 형식을 정의합니다. C# 10에서는 record class 구문을 동의어로 사용하여 참조 형식을 명확히 하고 record struct를 사용하여 유사한 기능을 가진 값 형식을 정의할 수 있습니다.

레코드에서 기본 생성자를 선언하면 컴파일러는 기본 생성자 매개 변수에 대한 공용 속성을 생성합니다. 레코드에 대한 기본 생성자 매개 변수를 위치 매개 변수라고 합니다. 컴파일러는 기본 생성자 또는 위치 매개 변수를 미러링하는 위치 속성을 만듭니다. 컴파일러는 record 한정자가 없는 형식에서 기본 생성자 매개 변수에 대한 속성을 합성하지 않습니다.

다음 두 예제에서는 record(또는 record class) 참조 형식을 보여 줍니다.

public record Person(string FirstName, string LastName);
public record Person
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
};

다음 두 예제에서는 record struct 값 형식을 보여 줍니다.

public readonly record struct Point(double X, double Y, double Z);
public record struct Point
{
    public double X { get; init; }
    public double Y { get; init; }
    public double Z { get; init; }
}

변경할 수 있는 속성 및 필드를 사용하여 레코드를 만들 수도 있습니다.

public record Person
{
    public required string FirstName { get; set; }
    public required string LastName { get; set; }
};

레코드 구조체는 변경 가능하기도 합니다. 이 경우 위치 레코드 구조체와 위치 매개 변수가 없는 레코드 구조체 둘 다일 수 있습니다.

public record struct DataMeasurement(DateTime TakenAt, double Measurement);
public record struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }
}

레코드를 변경할 수 있지만, 레코드의 기본적인 의도는 변경할 수 없는 데이터 모델을 지원하는 것입니다. 레코드 형식은 다음과 같은 기능을 제공합니다.

이전 예제는 참조 형식인 레코드와 값 형식인 레코드 간의 몇 가지 차이점을 보여줍니다.

  • record 또는 record class는 참조 형식을 선언합니다. class 키워드는 선택 사항이지만 읽는 사람에게 명확성을 줄 수 있습니다. record struct는 값 형식을 선언합니다.
  • 위치 속성은 record classreadonly record struct에서 ‘변경 불가능’합니다. record struct에서는 ‘변경 가능’합니다.

이 문서의 나머지 부분에서는 record class 형식과 record struct 형식에 대해 설명합니다. 차이점은 각 섹션에 자세히 설명되어 있습니다. classstruct 중에서 결정하는 것과 비슷하게 record classrecord struct 중에서 결정해야 합니다. ‘레코드’라는 용어는 모든 레코드 형식에 적용되는 동작을 설명하는 데 사용됩니다. record struct 또는 record class는 각각 구조체 또는 클래스 형식에만 적용되는 동작을 설명하는 데 사용됩니다. record struct 형식은 C# 10에서 도입되었습니다.

속성 정의에 대한 위치 구문

위치 매개 변수를 사용하여 레코드의 속성을 선언하고 인스턴스를 만들 때 속성 값을 초기화할 수 있습니다.

public record Person(string FirstName, string LastName);

public static void Main()
{
    Person person = new("Nancy", "Davolio");
    Console.WriteLine(person);
    // output: Person { FirstName = Nancy, LastName = Davolio }
}

속성 정의에 위치 구문을 사용하는 경우 컴파일러가 다음을 만듭니다.

  • 레코드 선언에 제공된 각 위치 매개 변수에 대한 공용 자동 구현 속성.
    • record 형식 및 readonly record struct 형식의 경우: 초기화 전용 속성입니다.
    • record struct 형식의 경우: 읽기/쓰기 속성입니다.
  • 매개 변수가 레코드 선언의 위치 매개 변수와 일치하는 기본 생성자.
  • 레코드 구조체 형식의 경우 각 필드를 기본값으로 설정하는 매개 변수가 없는 생성자입니다.
  • 레코드 선언에 제공된 각 위치 매개 변수에 대한 out 매개 변수를 사용하는 Deconstruct 메서드. 메서드는 위치 구문을 사용하여 정의된 속성을 분해합니다. 표준 속성 구문을 사용하여 정의된 속성은 무시됩니다.

컴파일러가 레코드 정의에서 만드는 이러한 요소에 특성을 추가하려고 할 수 있습니다. 위치 레코드의 속성에 적용하는 특성에 ‘대상’을 추가할 수 있습니다. 다음 예제에서는 Person 레코드의 각 속성에 System.Text.Json.Serialization.JsonPropertyNameAttribute를 적용합니다. property: 대상은 특성이 컴파일러 생성 속성에 적용됨을 나타냅니다. 다른 값은 필드에 특성을 적용하기 위한 field: 및 매개 변수에 특성을 적용하기 위한 param:입니다.

/// <summary>
/// Person record type
/// </summary>
/// <param name="FirstName">First Name</param>
/// <param name="LastName">Last Name</param>
/// <remarks>
/// The person type is a positional record containing the
/// properties for the first and last name. Those properties
/// map to the JSON elements "firstName" and "lastName" when
/// serialized or deserialized.
/// </remarks>
public record Person([property: JsonPropertyName("firstName")] string FirstName, 
    [property: JsonPropertyName("lastName")] string LastName);

위의 예제에서는 레코드에 대한 XML 문서 주석을 만드는 방법도 보여 줍니다. <param> 태그를 추가하여 기본 생성자의 매개 변수에 대한 문서를 추가할 수 있습니다.

생성된 자동 구현 속성 정의가 원하는 내용이 아니면 동일한 이름의 속성을 직접 정의할 수 있습니다. 예를 들어 내게 필요한 옵션이나 가변성을 변경하거나 get 또는 set 접근자에 대한 구현을 제공할 수 있습니다. 원본에서 속성을 선언하는 경우에는 레코드의 위치 매개 변수에서 초기화해야 합니다. 속성이 자동 구체화된 속성인 경우 속성을 초기화해야 합니다. 원본에 지원 필드를 추가하는 경우 지원 필드를 초기화해야 합니다. 생성된 분해자는 속성 정의를 사용합니다. 예를 들어 다음 예제에서는 위치 레코드 publicFirstNameLastName 속성을 선언하지만 Id 위치 매개 변수를 internal로 제한합니다. 레코드 및 레코드 구조체 형식에 대해 이 구문을 사용할 수 있습니다.

public record Person(string FirstName, string LastName, string Id)
{
    internal string Id { get; init; } = Id;
}

public static void Main()
{
    Person person = new("Nancy", "Davolio", "12345");
    Console.WriteLine(person.FirstName); //output: Nancy

}

레코드 형식은 위치 속성을 선언할 필요가 없습니다. 다음 예제와 같이 위치 속성 없이 레코드를 선언하고 기타 필드 및 속성을 선언할 수 있습니다.

public record Person(string FirstName, string LastName)
{
    public string[] PhoneNumbers { get; init; } = [];
};

표준 속성 구문을 사용하여 속성을 정의하지만 액세스 한정자를 생략하면 속성은 암시적으로 private입니다.

불변성

‘위치 레코드’ 및 ‘위치 읽기 전용 레코드 구조체’는 초기화 전용 속성을 선언합니다. ‘위치 레코드 구조체’는 읽기-쓰기 속성을 선언합니다. 이전 섹션에 표시된 대로 이 두 기본값 중 하나를 재정의할 수 있습니다.

불변성은 데이터 중심 형식이 스레드로부터 안전해야 하거나 해시 테이블에서 동일하게 남아 있는 해시 코드를 사용하는 경우에 유용할 수 있습니다. 하지만 불변성이 모든 데이터 시나리오에 적합한 것은 아닙니다. 예를 들어, Entity Framework Core는 변경할 수 없는 엔터티 형식을 사용한 업데이트를 지원하지 않습니다.

위치 매개 변수(record classreadonly record struct)에서 생성되든 init 접근자를 지정하여 생성되든, 초기화 전용 속성에는 ‘단순 불변성’이 있습니다. 초기화 후에는 값 형식 속성의 값 또는 참조 형식 속성의 참조를 변경할 수 없습니다. 그러나 참조 형식 속성이 참조하는 데이터는 변경할 수 있습니다. 다음 예제에서는 참조 형식 변경 불가능 속성의 콘텐츠(이 경우에는 배열)를 변경할 수 있음을 보여 줍니다.

public record Person(string FirstName, string LastName, string[] PhoneNumbers);

public static void Main()
{
    Person person = new("Nancy", "Davolio", new string[1] { "555-1234" });
    Console.WriteLine(person.PhoneNumbers[0]); // output: 555-1234

    person.PhoneNumbers[0] = "555-6789";
    Console.WriteLine(person.PhoneNumbers[0]); // output: 555-6789
}

레코드 형식에 고유한 기능이 컴파일러 합성 메서드로 구현되고, 이러한 메서드는 개체 상태를 수정하여 불변성을 손상하지 않습니다. 지정하지 않으면 record, record struct, readonly record struct 선언에 대해 합성된 메서드가 생성됩니다.

값 같음

같음 메서드를 재정의하거나 대체하지 않으면 선언하는 형식이 같음 정의 방법을 제어합니다.

  • class 형식의 경우 두 개체가 메모리에서 동일한 개체를 참조하면 두 개체는 동일합니다.
  • struct 형식의 경우 두 개체가 동일한 형식을 갖고 동일한 값을 저장하면 두 개체는 동일합니다.
  • record 한정자(record class, record structreadonly record struct)가 있는 형식의 경우 두 개체가 동일한 형식이고 동일한 값을 저장하는 경우 두 개체는 동일합니다.

record struct에 대한 같음의 정의는 struct에 대한 정의와 동일합니다. 차이점은 struct의 경우 구현이 ValueType.Equals(Object)에 있고 리플렉션에 의존한다는 것입니다. 레코드의 경우 구현은 컴파일러가 합성하며 선언된 데이터 멤버를 사용합니다.

일부 데이터 모델에서는 참조 같음이 필요합니다. 예를 들어, Entity Framework Core는 참조 같음을 사용하여 개념적으로 하나의 엔터티에 해당하는 엔터티 형식의 인스턴스를 하나만 사용하는지 확인합니다. 이러한 이유로 레코드 및 레코드 구조체는 Entity Framework Core에서 엔터티 형식으로 사용하기에 적절하지 않습니다.

다음 예제에서는 레코드 형식의 값 같음을 보여 줍니다.

public record Person(string FirstName, string LastName, string[] PhoneNumbers);

public static void Main()
{
    var phoneNumbers = new string[2];
    Person person1 = new("Nancy", "Davolio", phoneNumbers);
    Person person2 = new("Nancy", "Davolio", phoneNumbers);
    Console.WriteLine(person1 == person2); // output: True

    person1.PhoneNumbers[0] = "555-1234";
    Console.WriteLine(person1 == person2); // output: True

    Console.WriteLine(ReferenceEquals(person1, person2)); // output: False
}

값 같음을 구현하기 위해 컴파일러는 다음을 비롯한 여러 메서드를 합성합니다.

  • Object.Equals(Object)의 재정의. 재정의가 명시적으로 선언된 경우 오류입니다.

    두 매개 변수가 모두 Null이 아닌 경우 이 메서드가 Object.Equals(Object, Object) 정적 메서드의 기본으로 사용됩니다.

  • R이(가) 레코드 형식인 virtual 또는 sealed, Equals(R? other). 이 메서드는 IEquatable<T>를 구현합니다. 이 메서드는 명시적으로 선언할 수 있습니다.

  • 레코드 형식이 기본 레코드 형식 Base, Equals(Base? other)에서 파생된 경우. 재정의가 명시적으로 선언된 경우 오류입니다. Equals(R? other)의 고유한 구현을 제공하는 경우 GetHashCode의 구현도 제공하세요.

  • Object.GetHashCode()의 재정의. 이 메서드는 명시적으로 선언할 수 있습니다.

  • 연산자 ==!=의 재정의. 연산자가 명시적으로 선언된 경우 오류입니다.

  • 레코드 형식이 기본 레코드 형식 protected override Type EqualityContract { get; };에서 파생된 경우. 이 속성은 명시적으로 선언할 수 있습니다. 자세한 내용은 상속 계층 구조의 같음을 참조하세요.

레코드 형식에 명시적으로 선언할 수 있는 합성된 메서드의 서명과 일치하는 메서드가 있는 경우 컴파일러는 메서드를 합성하지 않습니다.

비파괴적 변경

약간의 수정이 있는 인스턴스를 복사해야 하는 경우 with 식을 사용하여 ‘비파괴적 변형’을 구현할 수 있습니다. with 식은 기존 레코드 인스턴스의 지정된 속성 및 필드가 수정된 복사본인 새 레코드 인스턴스를 만듭니다. 다음 예제와 같이 개체 이니셜라이저 구문을 사용하여 변경할 값을 지정합니다.

public record Person(string FirstName, string LastName)
{
    public string[] PhoneNumbers { get; init; }
}

public static void Main()
{
    Person person1 = new("Nancy", "Davolio") { PhoneNumbers = new string[1] };
    Console.WriteLine(person1);
    // output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] }

    Person person2 = person1 with { FirstName = "John" };
    Console.WriteLine(person2);
    // output: Person { FirstName = John, LastName = Davolio, PhoneNumbers = System.String[] }
    Console.WriteLine(person1 == person2); // output: False

    person2 = person1 with { PhoneNumbers = new string[1] };
    Console.WriteLine(person2);
    // output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] }
    Console.WriteLine(person1 == person2); // output: False

    person2 = person1 with { };
    Console.WriteLine(person1 == person2); // output: True
}

with 식은 위치 속성 또는 표준 속성 구문을 사용하여 만든 속성을 설정할 수 있습니다. 명시적으로 선언된 속성에는 with 식에서 변경할 init 또는 set 접근자가 있어야 합니다.

with 식의 결과는 ‘단순 복사본’입니다. 즉, 참조 속성의 경우 인스턴스에 대한 참조만 복사됩니다. 원본 레코드와 복사본은 모두 동일한 인스턴스를 참조합니다.

record class 형식에 대해 이 기능을 구현하기 위해 컴파일러는 clone 메서드와 복사 생성자를 합성합니다. 가상 clone 메서드는 복사 생성자에 의해 초기화된 새 레코드를 반환합니다. with 식을 사용하는 경우 컴파일러는 clone 메서드를 호출하는 코드를 만든 다음, with 식에 지정된 속성을 설정합니다.

다른 복사 동작이 필요한 경우 record class에서 고유한 복사 생성자를 작성할 수 있습니다. 이렇게 하면 컴파일러는 생성자를 합성하지 않습니다. 레코드가 sealed면 생성자를 private로 설정하고 그렇지 않으면 protected로 설정합니다. 컴파일러는 record struct 형식에 대해 복사 생성자를 합성하지 않습니다. 작성할 수도 있지만, 컴파일러가 with 식에 대해 이것의 호출을 생성하지 않습니다. record struct의 값은 할당할 때 복사됩니다.

clone 메서드를 재정의할 수 없으며, 어떤 레코드 형식으로도 Clone이라는 멤버를 만들 수 없습니다. Clone 메서드의 실제 이름은 컴파일러에서 생성합니다.

표시를 위한 기본 제공 형식

레코드 형식에는 공용 속성 및 필드의 이름과 값을 표시하는 컴파일러 생성 ToString 메서드가 있습니다. ToString 메서드는 다음 형식의 문자열을 반환합니다.

<레코드 형식 이름> { <속성 이름> = <값>, <속성 이름> = <값>, ...}

<value>에 대해 인쇄되는 문자열은 속성의 형식에 대해 ToString()에서 반환하는 문자열입니다. 다음 예제에서 ChildNamesSystem.Array이며, 여기서 ToStringSystem.String[]을 반환합니다.

Person { FirstName = Nancy, LastName = Davolio, ChildNames = System.String[] }

record class 형식에서 이 기능을 구현하기 위해 컴파일러는 가상 PrintMembers 메서드와 ToString 재정의를 합성합니다. record struct 형식에서 이 멤버는 private입니다. ToString 재정의는 형식 이름과 그 뒤의 여는 괄호를 사용하여 StringBuilder 개체를 만듭니다. PrintMembers를 호출하여 속성 이름 및 값을 추가한 다음 닫는 괄호를 추가합니다. 다음 예제에서는 합성된 재정의에 포함된 것과 유사한 코드를 보여 줍니다.

public override string ToString()
{
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.Append("Teacher"); // type name
    stringBuilder.Append(" { ");
    if (PrintMembers(stringBuilder))
    {
        stringBuilder.Append(" ");
    }
    stringBuilder.Append("}");
    return stringBuilder.ToString();
}

PrintMembers 또는 ToString 재정의의 고유한 구현을 제공할 수 있습니다. 예제는 이 문서의 뒷부분에 나오는 파생 레코드의 PrintMembers 형식 섹션에 나와 있습니다. C# 10 이상에서 ToString의 구현에는 sealed 한정자가 포함될 수 있으며, 컴파일러가 파생된 레코드에 대해 ToString 구현을 합성할 수 없도록 합니다. record 형식의 계층 구조 전체에서 일관된 문자열 표현을 만들 수 있습니다. (파생 레코드에는 여전히 모든 파생 속성에 대해 생성된 PrintMembers 메서드가 있습니다.)

상속

이 섹션은 record class 형식에만 적용됩니다.

레코드는 다른 레코드에서 상속될 수 있습니다. 그러나 레코드는 클래스에서 상속될 수 없고 클래스는 레코드에서 상속될 수 없습니다.

파생 레코드 형식의 위치 매개 변수

파생 레코드는 기본 레코드 기본 생성자의 모든 매개 변수에 대한 위치 매개 변수를 선언합니다. 기본 레코드는 이러한 속성을 선언하고 초기화합니다. 파생 레코드는 이러한 속성을 숨기지는 않지만 기본 레코드에 선언되지 않은 매개 변수의 속성만 만들고 초기화합니다.

다음 예제에서는 위치 속성 구문을 사용한 상속을 보여 줍니다.

public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", 3);
    Console.WriteLine(teacher);
    // output: Teacher { FirstName = Nancy, LastName = Davolio, Grade = 3 }
}

상속 계층 구조에서의 같음

이 섹션은 record class 형식에 적용되지만 record struct 형식에는 적용되지 않습니다. 두 레코드 변수가 같으려면 런타임 형식이 동일해야 합니다. 포함하는 변수의 형식은 다를 수 있습니다. 다음 코드 예제에서는 상속된 같음 비교가 설명되어 있습니다.

public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", 3);
    Person student = new Student("Nancy", "Davolio", 3);
    Console.WriteLine(teacher == student); // output: False

    Student student2 = new Student("Nancy", "Davolio", 3);
    Console.WriteLine(student2 == student); // output: True
}

예제에서는 인스턴스가 Student 또는 Teacher의 파생 형식인 경우에도 모든 변수는 Person으로 선언됩니다. 인스턴스에는 동일한 속성 및 동일한 속성 값이 있습니다. 그러나 모두 Person 형식 변수이지만 student == teacherFalse를 반환하고, 하나는 Person 변수이고 하나는 Student 변수이지만 student == student2True를 반환합니다. 같음 테스트는 변수의 선언된 형식이 아니라 실제 개체의 런타임 형식에 따라 달라집니다.

이 동작을 구현하기 위해 컴파일러는 레코드 형식과 일치하는 Type 개체를 반환하는 EqualityContract 속성을 합성합니다. EqualityContract를 사용하면 같음 메서드는 같음을 확인할 때 개체의 런타임 형식을 비교할 수 있습니다. 레코드의 기본 형식이 object이면 이 속성은 virtual입니다. 기본 형식이 또 다른 레코드 형식이면 이 속성은 재정의입니다. 레코드 형식이 sealed면 형식은 sealed가 되므로, 이 속성은 실제로는 sealed가 됩니다.

코드가 파생 형식의 두 인스턴스를 비교할 때 합성된 같음 메서드는 기본의 모든 데이터 멤버와 파생 형식의 같음을 확인합니다. 합성된 GetHashCode 메서드는 기본 형식 및 파생 레코드 형식에 선언된 모든 데이터 멤버의 GetHashCode 메서드를 사용합니다. record 데이터 멤버에는 자동 구체화된 속성에 대해 선언된 모든 필드와 컴파일러 합성 지원 필드가 포함됩니다.

파생 레코드의 with

with 식의 결과는 식의 피연산자와 동일한 런타임 형식을 갖습니다. 런타임 형식의 속성은 모두 복사되지만 다음 예제와 같이 컴파일 시간 형식의 속성만 설정할 수 있습니다.

public record Point(int X, int Y)
{
    public int Zbase { get; set; }
};
public record NamedPoint(string Name, int X, int Y) : Point(X, Y)
{
    public int Zderived { get; set; }
};

public static void Main()
{
    Point p1 = new NamedPoint("A", 1, 2) { Zbase = 3, Zderived = 4 };

    Point p2 = p1 with { X = 5, Y = 6, Zbase = 7 }; // Can't set Name or Zderived
    Console.WriteLine(p2 is NamedPoint);  // output: True
    Console.WriteLine(p2);
    // output: NamedPoint { X = 5, Y = 6, Zbase = 7, Name = A, Zderived = 4 }

    Point p3 = (NamedPoint)p1 with { Name = "B", X = 5, Y = 6, Zbase = 7, Zderived = 8 };
    Console.WriteLine(p3);
    // output: NamedPoint { X = 5, Y = 6, Zbase = 7, Name = B, Zderived = 8 }
}

파생 레코드의 PrintMembers 형식

파생 레코드 형식의 합성된 PrintMembers 메서드는 기본 구현을 호출합니다. 그러면 다음 예제와 같이 파생된 형식과 기본 형식의 모든 공용 속성 및 필드가 ToString 출력에 포함됩니다.

public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);

public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", 3);
    Console.WriteLine(teacher);
    // output: Teacher { FirstName = Nancy, LastName = Davolio, Grade = 3 }
}

PrintMembers 메서드의 고유한 구현을 제공할 수 있습니다. 이렇게 하려면 다음 서명을 사용합니다.

  • object에서 파생되는 sealed 레코드(기본 레코드를 선언하지 않음): private bool PrintMembers(StringBuilder builder)
  • 다른 레코드에서 파생되는 sealed 레코드의 경우(바깥쪽 형식이 sealed이므로 메서드는 실제로는 sealed입니다): protected override bool PrintMembers(StringBuilder builder)
  • sealed가 아니고 개체에서 파생된 레코드: protected virtual bool PrintMembers(StringBuilder builder);
  • sealed가 아니고 다른 레코드에서 파생된 레코드: protected override bool PrintMembers(StringBuilder builder);

다음은 합성된 PrintMembers 메서드를 대체하는 코드의 예제입니다. 하나는 개체에서 파생되는 레코드 형식에 대한 것이고 다른 하나는 다른 레코드에서 파생되는 레코드 형식에 대한 것입니다.

public abstract record Person(string FirstName, string LastName, string[] PhoneNumbers)
{
    protected virtual bool PrintMembers(StringBuilder stringBuilder)
    {
        stringBuilder.Append($"FirstName = {FirstName}, LastName = {LastName}, ");
        stringBuilder.Append($"PhoneNumber1 = {PhoneNumbers[0]}, PhoneNumber2 = {PhoneNumbers[1]}");
        return true;
    }
}

public record Teacher(string FirstName, string LastName, string[] PhoneNumbers, int Grade)
    : Person(FirstName, LastName, PhoneNumbers)
{
    protected override bool PrintMembers(StringBuilder stringBuilder)
    {
        if (base.PrintMembers(stringBuilder))
        {
            stringBuilder.Append(", ");
        };
        stringBuilder.Append($"Grade = {Grade}");
        return true;
    }
};

public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", new string[2] { "555-1234", "555-6789" }, 3);
    Console.WriteLine(teacher);
    // output: Teacher { FirstName = Nancy, LastName = Davolio, PhoneNumber1 = 555-1234, PhoneNumber2 = 555-6789, Grade = 3 }
}

참고 항목

C# 10.0 이상에서 컴파일러는 기본 레코드가 ToString 메서드를 봉인한 경우에도 파생된 레코드에서 PrintMembers를 합성합니다. PrintMembers의 고유한 구현을 만들 수도 있습니다.

파생 레코드의 분해자 동작

파생 레코드의 Deconstruct 메서드는 컴파일 시간 형식의 모든 위치 속성 값을 반환합니다. 변수 형식이 기본 레코드인 경우 개체가 파생 형식으로 캐스팅되는 경우를 제외하고는 기본 레코드 속성만 분해됩니다. 다음 예제에서는 파생 레코드에 대해 분해자를 호출하는 방법을 보여 줍니다.

public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
    : Person(FirstName, LastName);

public static void Main()
{
    Person teacher = new Teacher("Nancy", "Davolio", 3);
    var (firstName, lastName) = teacher; // Doesn't deconstruct Grade
    Console.WriteLine($"{firstName}, {lastName}");// output: Nancy, Davolio

    var (fName, lName, grade) = (Teacher)teacher;
    Console.WriteLine($"{fName}, {lName}, {grade}");// output: Nancy, Davolio, 3
}

제네릭 제약 조건

record 키워드는 class 또는 struct 형식의 한정자입니다. record 한정자를 추가하면 이 문서의 앞부분에서 설명한 동작이 포함됩니다. 형식이 레코드가 되어야 하는 제네릭 제약 조건은 없습니다. record class은(는) class 제약 조건을 충족합니다. record struct은(는) struct 제약 조건을 충족합니다. 자세한 내용은 형식 매개 변수에 대한 제약 조건을 참조하세요.

C# 언어 사양

자세한 내용은 C# 언어 사양클래스 섹션을 참조하세요.

이러한 기능에 대한 자세한 내용은 다음 기능 제안 노트를 참조하세요.

참고 항목