Record

2022. 1. 21. 00:00CSharp/Advance

반응형

C# 9부터 record 키워드를 사용하여 데이터를 캡슐화하는 기본 제공 기능을 제공하는 참조 형식을 정의합니다. 위치 매개 변수 또는 표준 속성 구문을 사용하여 변경할 수 없는 속성이 있는 레코드 형식을 만들 수 있습니다.

// Record 선언 (간단한 방식)
public record Person(string FirstName, string LastName);
// Record 선언 다른 방식
//public record Person
//{
//    public string FirstName { get; init; } = default!;
//    public string LastName { get; init; } = default!;
//};

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

public record Person
{
    public string FirstName { get; set; } = default!;
    public string LastName { get; set; } = default!;
};

C# 10 이상에서는 위치 매개 변수 또는 표준 속성 구문을 사용하여 record struct 형식을 정의할 수 있다.

// struct 도 record 로 만들 수 있다.
// readonly 속성을 통해 변경 불가능 하다.
public readonly record struct Point(double X, double Y, double Z);
// set 을 init 으로 선언하여 변경 불가능 하게 만든다.
//public record struct Point
//{
//    public double X { get; init; }
//    public double Y { get; init; }
//    public double Z { get; init; }
//}
// 변경 가능한 record struct
public record struct Point(double X, double Y, double Z);
//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 class  readonly record struct에서 ‘변경 불가능’. record struct에서는 ‘변경 가능’.

위의 내용을 record class 로 선언하면..

// 변경 불가능 record class (readonly 가 없어도 기본적으로 불가능)
public record class Point(double X, double Y, double Z);

// class 는 생략 가능
//public record Point(double X, double Y, double Z);

record 에 attribute 를 추가해서 선언 할 수도 있다. 

/// <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);

 

생성된 자동 구현 속성 정의가 원하는 내용이 아니면 동일한 이름의 속성을 직접 정의할 수 있다. 

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

불변성

초기화 후에는 값 형식 속성의 값 또는 참조 형식 속성의 참조를 변경할 수 없다.

그러나 참조 형식 속성이 참조하는 데이터는 변경할 수 있다. 

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.FirstName = "1234"; // 이것은 변경 불가 Compile error
    //person.LastName = "1234"; // 이것은 변경 불가 Compile error
    
    person.PhoneNumbers[0] = "555-6789"; // 이것은 변경 가능 (참조 형식 속성이 참조하는 데이터)
    Console.WriteLine(person.PhoneNumbers[0]); // output: 555-6789
}

값 같음

  • class 형식의 경우 두 개체가 메모리에서 동일한 개체를 참조하면 두 개체는 동일
  • struct 형식의 경우 두 개체가 동일한 형식을 갖고 동일한 값을 저장하면 두 개체는 동일
  • record struct  readonly record struct를 포함하는 record 형식의 경우 두 개체가 동일한 형식을 갖고 동일한 값을 저장하면 두 개체는 동일

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

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
}

Nondestructive mutation

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
}

 

상속

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 }
}

 

파생 레코드의 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 }
}

파생 레코드의 분해자 동작

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
}

관련영상

https://youtu.be/CAA4HjHppQ8

 

 

 

반응형

'CSharp > Advance' 카테고리의 다른 글

제네릭 클래스 (Generic class)  (0) 2022.01.25
제네릭 컬렉션 (Generic Collection)  (0) 2022.01.24
Lambda advanced  (0) 2022.01.20
Action, Func, Lambda  (0) 2022.01.19
Event (이벤트)  (0) 2022.01.18