다음을 통해 공유


레코드 형식 만들기

레코드값 기반 같음사용하는 형식입니다. 레코드를 참조 형식 또는 값 형식으로 정의할 수 있습니다. 레코드 형식 정의가 동일하고 모든 필드에 대해 두 레코드의 값이 같으면 레코드 형식의 두 변수가 동일합니다. 참조된 개체가 동일한 클래스 형식이고 변수가 동일한 개체를 참조하는 경우 클래스 형식의 두 변수가 동일합니다. 값 기반 동등성은 레코드 형식에서 원하는 다른 기능을 의미합니다. 컴파일러는 class대신 record 선언할 때 많은 멤버를 생성합니다. 컴파일러는 record struct 형식에 대해 동일한 메서드를 생성합니다.

이 자습서에서는 다음 방법을 알아봅니다.

  • class 형식에 record 한정자를 추가할지 여부를 결정합니다.
  • 레코드 형식 및 위치 레코드 형식을 선언합니다.
  • 레코드에서 컴파일러가 생성한 메서드를 사용자의 메서드로 대체합니다.

필수 구성 요소

.NET 6 이상을 실행하도록 컴퓨터를 설정해야 합니다. C# 컴파일러는 Visual Studio 2022 또는 .NET SDK사용할 수 있습니다.

레코드의 특징

record 키워드를 사용하여 형식을 선언하고 class 또는 struct 선언을 수정하여 레코드 정의합니다. 필요에 따라 class 키워드를 생략하여 record class만들 수 있습니다. 레코드는 값 기반 동등성 의미 체계를 따릅니다. 값 의미 체계를 적용하기 위해 컴파일러는 레코드 형식에 대한 여러 메서드를 생성합니다(record class 형식 및 record struct 형식 모두).

레코드는 Object.ToString()재정의도 제공합니다. 컴파일러는 Object.ToString()사용하여 레코드를 표시하는 메서드를 합성합니다. 이 자습서의 코드를 작성할 때 해당 멤버를 탐색합니다. 레코드는 비파괴적 변형을 가능하게 하기 위해 with 표현식을 지원합니다.

위치 레코드는 더 간결한 구문을 사용하여 선언할 수도 있습니다. 컴파일러는 위치 레코드를 선언할 때 더 많은 메서드를 합성합니다.

  • 매개 변수가 레코드 선언의 위치 매개 변수와 일치하는 기본 생성자입니다.
  • 기본 생성자의 각 매개 변수에 대한 공용 속성입니다. 이러한 속성은 초기화 전용인으로, record class 형식 및 readonly record struct 형식에 적용됩니다. 형식의 경우 읽기/쓰기.
  • 레코드에서 속성을 추출하는 Deconstruct 메서드입니다.

온도 데이터 빌드

데이터 및 통계는 레코드를 사용하려는 시나리오 중 하나입니다. 이 자습서에서는 다양한 용도로 도 일을 계산하는 애플리케이션을 빌드합니다. 도 일 일, 주 또는 개월의 기간 동안 열의 측정 (또는 열 부족)입니다. 도 일수는 에너지 사용량을 추적하고 예측합니다. 더운 날은 에어컨이 더 많고 추운 날은 용광로 사용량이 더 많은 것을 의미합니다. 학위 일 식물 인구를 관리 하 고 계절 변화에 따라 식물 성장에 상관 관계를 도움이 됩니다. 학위 일 기후와 일치 하기 위해 여행 하는 종에 대 한 동물 이주를 추적 하는 데 도움이.

수식은 지정된 날짜의 평균 온도와 기준 온도를 기반으로 합니다. 시간에 따른 온도를 계산하려면 일정 기간 동안 매일 높고 낮은 온도가 필요합니다. 먼저 새 애플리케이션을 만들어 보겠습니다. 새 콘솔 애플리케이션을 만듭니다. "DailyTemperature.cs"라는 새 파일에 새 레코드 형식을 만듭니다.

public readonly record struct DailyTemperature(double HighTemp, double LowTemp);

앞의 코드는 위치 레코드을 정의하는 것입니다. DailyTemperature 레코드는 상속할 의도가 없으며 변경할 수 없기 때문에 readonly record struct타입입니다. HighTempLowTemp 속성은 초기화 전용 속성입니다. 즉, 생성자에서 설정하거나 속성 이니셜라이저를 사용할 수 있습니다. 위치 매개 변수를 읽기/쓰기로 하려면 readonly record struct대신 record struct 선언합니다. 또한 DailyTemperature 형식에는 두 속성과 일치하는 두 개의 매개 변수가 있는 기본 생성자 있습니다. 기본 생성자를 사용하여 DailyTemperature 레코드를 초기화합니다. 다음 코드는 여러 DailyTemperature 레코드를 만들고 초기화합니다. 첫 번째는 명명된 매개 변수를 사용하여 HighTempLowTemp을 명확히 합니다. 나머지 이니셜라이저는 위치 매개 변수를 사용하여 HighTempLowTemp을 초기화합니다.

private static DailyTemperature[] data = [
    new DailyTemperature(HighTemp: 57, LowTemp: 30), 
    new DailyTemperature(60, 35),
    new DailyTemperature(63, 33),
    new DailyTemperature(68, 29),
    new DailyTemperature(72, 47),
    new DailyTemperature(75, 55),
    new DailyTemperature(77, 55),
    new DailyTemperature(72, 58),
    new DailyTemperature(70, 47),
    new DailyTemperature(77, 59),
    new DailyTemperature(85, 65),
    new DailyTemperature(87, 65),
    new DailyTemperature(85, 72),
    new DailyTemperature(83, 68),
    new DailyTemperature(77, 65),
    new DailyTemperature(72, 58),
    new DailyTemperature(77, 55),
    new DailyTemperature(76, 53),
    new DailyTemperature(80, 60),
    new DailyTemperature(85, 66) 
];

위치 레코드를 포함하여 레코드에 고유한 속성 또는 메서드를 추가할 수 있습니다. 매일 평균 온도를 계산해야 합니다. DailyTemperature 레코드에 해당 속성을 추가할 수 있습니다.

public readonly record struct DailyTemperature(double HighTemp, double LowTemp)
{
    public double Mean => (HighTemp + LowTemp) / 2.0;
}

이 데이터를 사용할 수 있는지 확인해 보겠습니다. Main 메서드에 다음 코드를 추가합니다.

foreach (var item in data)
    Console.WriteLine(item);

애플리케이션을 실행하면, 공간 때문에 여러 행이 제거된 다음 화면과 비슷한 출력을 볼 수 있습니다.

DailyTemperature { HighTemp = 57, LowTemp = 30, Mean = 43.5 }
DailyTemperature { HighTemp = 60, LowTemp = 35, Mean = 47.5 }


DailyTemperature { HighTemp = 80, LowTemp = 60, Mean = 70 }
DailyTemperature { HighTemp = 85, LowTemp = 66, Mean = 75.5 }

이전 코드는 컴파일러에 의해 합성된 ToString의 재정의를 출력한 것입니다. 다른 텍스트를 선호하는 경우, 컴파일러가 자동 생성하지 않도록 자신만의 고유한 버전의 ToString을 작성할 수 있습니다.

컴퓨팅 학위 일수

도 일을 계산하려면 기준 온도와 지정된 날짜의 평균 온도의 차이를 계산합니다. 시간에 따른 열을 측정하려면 평균 온도가 기준선보다 낮은 모든 날짜를 삭제합니다. 시간에 따른 추위를 측정하려면 평균 온도가 기준선보다 높은 모든 날짜를 삭제합니다. 예를 들어 미국은 65F를 난방 및 냉방도 일의 기준으로 사용합니다. 이는 난방이나 냉각이 필요하지 않은 온도입니다. 하루의 평균 온도가 70 F이면, 그 날은 5 냉각도 일과 제로 난방도 일입니다. 반대로, 평균 온도가 55 F이면, 그 날은 10 가열도 일과 0 냉각도 일입니다.

이러한 수식을 레코드 유형의 작은 계층 구조로 표현할 수 있습니다. 즉, 추상적인 도일 유형과 난방 도일 및 냉각 도일을 위한 두 가지 구체적인 유형입니다. 이러한 형식은 위치 레코드일 수도 있습니다. 기준 온도와 일별 온도 레코드 시퀀스를 기본 생성자에 대한 인수로 사용합니다.

public abstract record DegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords);

public sealed record HeatingDegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords)
    : DegreeDays(BaseTemperature, TempRecords)
{
    public double DegreeDays => TempRecords.Where(s => s.Mean < BaseTemperature).Sum(s => BaseTemperature - s.Mean);
}

public sealed record CoolingDegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords)
    : DegreeDays(BaseTemperature, TempRecords)
{
    public double DegreeDays => TempRecords.Where(s => s.Mean > BaseTemperature).Sum(s => s.Mean - BaseTemperature);
}

추상 DegreeDays 레코드는 HeatingDegreeDays 레코드와 CoolingDegreeDays 레코드 모두에 대한 공유 기본 클래스입니다. 파생 레코드의 기본 생성자 선언은 기본 레코드 초기화를 관리하는 방법을 보여 줍니다. 파생 레코드는 기본 레코드 기본 생성자의 모든 매개 변수에 대한 매개 변수를 선언합니다. 기본 레코드는 해당 속성을 선언하고 초기화합니다. 파생 레코드는 숨기지 않지만 기본 레코드에 선언되지 않은 매개 변수에 대한 속성만 만들고 초기화합니다. 이 예제에서 파생 레코드는 새 기본 생성자 매개 변수를 추가하지 않습니다. Main 메서드에 다음 코드를 추가하여 코드를 테스트합니다.

var heatingDegreeDays = new HeatingDegreeDays(65, data);
Console.WriteLine(heatingDegreeDays);

var coolingDegreeDays = new CoolingDegreeDays(65, data);
Console.WriteLine(coolingDegreeDays);

다음과 같은 출력이 표시됩니다.

HeatingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 85 }
CoolingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 71.5 }

컴파일러 합성 메서드 정의

코드는 해당 기간 동안의 올바른 난방 및 냉각 수준 일 수를 계산합니다. 그러나 이 예제에서는 레코드에 대해 합성된 메서드 중 일부를 대체할 수 있는 이유를 보여 줍니다. 클론 메서드를 제외한 레코드 형식에서 컴파일러 합성 메서드의 고유한 버전을 선언할 수 있습니다. clone 메서드는 컴파일러에서 생성된 이름을 가지며 다른 구현을 제공할 수 없습니다. 이러한 합성된 메서드에는 복사 생성자, System.IEquatable<T> 인터페이스의 멤버, 같음 및 같지 않음 테스트 및 GetHashCode()포함됩니다. 당신은 이를 위해 PrintMembers을(를) 합성합니다. 고유한 ToString을 선언할 수도 있지만, 상속 시나리오에서 PrintMembers이 더 나은 옵션을 제공합니다. 합성된 메서드의 고유한 버전을 제공하려면 서명이 합성된 메서드와 일치해야 합니다.

콘솔 출력의 TempRecords 요소는 유용하지 않습니다. 형식을 표시하지만 다른 항목은 표시하지 않습니다. 합성된 PrintMembers 메서드의 고유한 구현을 제공하여 이 동작을 변경할 수 있습니다. 서명은 record 선언에 적용된 한정자에 따라 달라집니다.

  • 레코드 형식이 sealed또는 record struct일 경우, 서명이 private bool PrintMembers(StringBuilder builder);입니다.
  • 레코드 형식이 sealed이 아니고 object에서 파생되는 경우(즉, 기본 레코드를 선언하지 않는다면), 서명은 protected virtual bool PrintMembers(StringBuilder builder);입니다.
  • 레코드 형식이 sealed 않고 다른 레코드에서 파생되는 경우 서명이 protected override bool PrintMembers(StringBuilder builder);

이러한 규칙은 PrintMembers의 목적을 이해하는 것이 가장 쉽게 이해할 수 있는 방법입니다. PrintMembers 레코드 형식의 각 속성에 대한 정보를 문자열에 추가합니다. 계약에서는 기본 레코드가 자신의 멤버를 표시 목록에 반드시 추가하도록 요구하며, 파생된 멤버들도 자신의 멤버를 추가하는 것을 가정합니다. 각 레코드 형식은 다음 예제에서 보이는 바와 같이 HeatingDegreeDays과 유사한 ToString 재정의를 생성합니다.

public override string ToString()
{
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.Append("HeatingDegreeDays");
    stringBuilder.Append(" { ");
    if (PrintMembers(stringBuilder))
    {
        stringBuilder.Append(" ");
    }
    stringBuilder.Append("}");
    return stringBuilder.ToString();
}

컬렉션의 형식을 출력하지 않는 DegreeDays 레코드에 PrintMembers 메서드를 선언합니다.

protected virtual bool PrintMembers(StringBuilder stringBuilder)
{
    stringBuilder.Append($"BaseTemperature = {BaseTemperature}");
    return true;
}

서명은 컴파일러의 버전과 일치하도록 virtual protected 메서드를 선언합니다. 접근자가 잘못되면 걱정하지 마세요. 언어는 올바른 서명을 적용합니다. 합성된 메서드에 대한 올바른 한정자를 잊어버린 경우 컴파일러는 올바른 서명을 얻는 데 도움이 되는 경고 또는 오류를 발생시킵니다.

레코드 형식에서 ToString 메서드를 sealed으로 선언할 수 있습니다. 그러면 파생된 레코드가 새 구현을 제공하지 못하게 됩니다. 파생 레코드는 여전히 PrintMembers 재정의된 값을 포함합니다. 레코드의 런타임 형식을 표시되지 않도록 하려면 ToString을 봉인할 것입니다. 앞의 예제에서는 레코드가 난방 또는 냉각도 일을 측정하던 위치에 대한 정보를 잃게 됩니다.

비폭력적 돌연변이

위치 레코드 클래스의 합성된 멤버는 레코드의 상태를 수정하지 않습니다. 목표는 변경할 수 없는 레코드를 더 쉽게 만들 수 있다는 것입니다. readonly record struct를 선언하여 변경할 수 없는 레코드 구조체를 만든다는 것을 기억하세요. HeatingDegreeDaysCoolingDegreeDays대한 이전 선언을 다시 살펴봅니다. 추가된 멤버는 레코드 값에 대해 계산을 수행하지만 상태를 변경하지는 않습니다. 위치 레코드를 사용하면 변경할 수 없는 참조 형식을 더 쉽게 만들 수 있습니다.

변경할 수 없는 참조 형식을 만든다는 것은 비파괴적 변경을 사용하려는 것입니다. 식을 사용하여 기존 레코드 인스턴스와 유사한 새 레코드 인스턴스를 만듭니다. 이러한 표현은 복사본에 추가 할당을 통해 수정을 가하는 복사 생성입니다. 그 결과 각 속성이 기존 레코드에서 복사되고 필요에 따라 수정된 새 레코드 인스턴스가 생성됩니다. 원래 레코드는 변경되지 않습니다.

with 식을 보여 주는 몇 가지 기능을 프로그램에 추가해 보겠습니다. 먼저 동일한 데이터를 사용하여 생장 온도일을 계산하는 새 기록을 만들어 보겠습니다. 증가 도 일 일반적으로 기준선으로 41 F를 사용하고 기준 위의 온도를 측정합니다. 동일한 데이터를 사용하려면 coolingDegreeDays유사하지만 기본 온도가 다른 새 레코드를 만들 수 있습니다.

// Growing degree days measure warming to determine plant growing rates
var growingDegreeDays = coolingDegreeDays with { BaseTemperature = 41 };
Console.WriteLine(growingDegreeDays);

계산된 도 수를 더 높은 기준 온도로 생성된 숫자와 비교할 수 있습니다. 레코드는 참조 형식이며 이러한 복사본은 단순 복사본입니다. 데이터의 배열은 복사되지 않지만 두 레코드 모두 동일한 데이터를 참조합니다. 이 사실은 다른 시나리오에서 장점입니다. 증가하는 도일의 경우 이전 5일 동안의 합계를 추적하는 것이 유용합니다. with 식을 사용하여 다른 원본 데이터로 새 레코드를 만들 수 있습니다. 다음 코드는 이러한 누적의 컬렉션을 빌드한 다음 값을 표시합니다.

// showing moving accumulation of 5 days using range syntax
List<CoolingDegreeDays> movingAccumulation = new();
int rangeSize = (data.Length > 5) ? 5 : data.Length;
for (int start = 0; start < data.Length - rangeSize; start++)
{
    var fiveDayTotal = growingDegreeDays with { TempRecords = data[start..(start + rangeSize)] };
    movingAccumulation.Add(fiveDayTotal);
}
Console.WriteLine();
Console.WriteLine("Total degree days in the last five days");
foreach(var item in movingAccumulation)
{
    Console.WriteLine(item);
}

with 식을 사용하여 레코드 복사본을 만들 수도 있습니다. with 식의 중괄호 사이에 속성을 지정하지 마세요. 즉, 복사본을 만들고 속성을 변경하지 않습니다.

var growingDegreeDaysCopy = growingDegreeDays with { };

완료된 애플리케이션을 실행하여 결과를 확인합니다.

요약

이 자습서에서는 레코드의 여러 측면을 보여 줍니다. 레코드는 기본 용도로 데이터를 저장하는 형식에 대한 간결한 구문을 제공합니다. 개체 지향 클래스의 경우 기본 용도는 책임을 정의하는 것입니다. 이 자습서에서는 간결한 구문을 사용하여 레코드의 속성을 선언할 수 있는 위치 레코드집중했습니다. 컴파일러는 레코드를 복사하고 비교하기 위해 레코드의 여러 멤버를 합성합니다. 레코드 형식에 필요한 다른 멤버를 추가할 수 있습니다. 컴파일러에서 생성된 멤버 중 어느 것도 상태를 변경할 수 없다는 것을 알고 변경할 수 없는 레코드 형식을 만들 수 있습니다. 그리고 with 표현식을 사용하면 비대화성 돌연변이를 쉽게 지원할 수 있습니다.

레코드는 형식을 정의하는 또 다른 방법을 추가합니다. class 정의를 사용하여 개체의 책임과 동작에 초점을 맞춘 개체 지향 계층 구조를 만듭니다. 데이터를 저장하고 효율적으로 복사할 수 있을 만큼 작은 데이터 구조에 대한 struct 형식을 만듭니다. 값 기반 같음 및 비교를 원하고, 값을 복사하지 않고, 참조 변수를 사용하려는 경우 record 형식을 만듭니다. 효율적으로 복사할 수 있을 만큼 작은 형식에 대한 레코드 기능을 원하는 경우 record struct 형식을 만듭니다.

레코드 타입에 대한 C# 언어 참조 문서와 제안된 레코드 형식 사양레코드 구조체 사양에 대해 자세히 알아볼 수 있습니다.