레코드 형식 만들기
레코드값 기반 같음사용하는 형식입니다. 레코드를 참조 형식 또는 값 형식으로 정의할 수 있습니다. 레코드 형식 정의가 동일하고 모든 필드에 대해 두 레코드의 값이 같으면 레코드 형식의 두 변수가 동일합니다. 참조된 개체가 동일한 클래스 형식이고 변수가 동일한 개체를 참조하는 경우 클래스 형식의 두 변수가 동일합니다. 값 기반 동등성은 레코드 형식에서 원하는 다른 기능을 의미합니다. 컴파일러는 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.Equals(Object)우선 적용입니다.
- 매개 변수가 레코드 형식인 가상
Equals
메서드입니다. - Object.GetHashCode()에 대한 재정의입니다.
-
operator ==
및operator !=
메서드 목록. - 레코드 형식은 System.IEquatable<T>을 구현합니다.
레코드는 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
타입입니다.
HighTemp
및 LowTemp
속성은 초기화 전용 속성입니다. 즉, 생성자에서 설정하거나 속성 이니셜라이저를 사용할 수 있습니다. 위치 매개 변수를 읽기/쓰기로 하려면 readonly record struct
대신 record struct
선언합니다. 또한 DailyTemperature
형식에는 두 속성과 일치하는 두 개의 매개 변수가 있는 기본 생성자 있습니다. 기본 생성자를 사용하여 DailyTemperature
레코드를 초기화합니다. 다음 코드는 여러 DailyTemperature
레코드를 만들고 초기화합니다. 첫 번째는 명명된 매개 변수를 사용하여 HighTemp
와 LowTemp
을 명확히 합니다. 나머지 이니셜라이저는 위치 매개 변수를 사용하여 HighTemp
과 LowTemp
을 초기화합니다.
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
를 선언하여 변경할 수 없는 레코드 구조체를 만든다는 것을 기억하세요.
HeatingDegreeDays
및 CoolingDegreeDays
대한 이전 선언을 다시 살펴봅니다. 추가된 멤버는 레코드 값에 대해 계산을 수행하지만 상태를 변경하지는 않습니다. 위치 레코드를 사용하면 변경할 수 없는 참조 형식을 더 쉽게 만들 수 있습니다.
변경할 수 없는 참조 형식을 만든다는 것은 비파괴적 변경을 사용하려는 것입니다.
with
식을 보여 주는 몇 가지 기능을 프로그램에 추가해 보겠습니다. 먼저 동일한 데이터를 사용하여 생장 온도일을 계산하는 새 기록을 만들어 보겠습니다.
증가 도 일 일반적으로 기준선으로 41 F를 사용하고 기준 위의 온도를 측정합니다. 동일한 데이터를 사용하려면 coolingDegreeDays
유사하지만 기본 온도가 다른 새 레코드를 만들 수 있습니다.
// Growing degree days measure warming to determine plant growing rates
var growingDegreeDays = coolingDegreeDays with { BaseTemperature = 41 };
Console.WriteLine(growingDegreeDays);
계산된 도 수를 더 높은 기준 온도로 생성된 숫자와 비교할 수 있습니다. 레코드는 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
형식을 만듭니다.
레코드 타입에 대한
.NET