Init-only properties
Object initializer는 사용자에게 객체 생성을 위한 유연하고 가독성있는 형식의 타입을 제공한다. 특히, 객체의 nested object 생성에 편리하다.
public class Person
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
var person = new Person { FirstName = "Mads", LastName = "Torgersen" };
한계 중 하나는, properties가 mutable이어야 한다는 것이다. Init-only properties로 해결할 수 있다.
init accessor는 객체 초기화 시에만 호출되는 set accessor의 변형이다.
public class Person
{
public string? FirstName { get; init; }
public string? LastName { get; init; }
}
var person = new Person { FirstName = "Mads", LastName = "Nielsen" }; // OK
person.LastName = "Torgersen"; // ERROR!
//Init-only property or indexer 'Person.LastName' can only be assigned
//in an object initializer, or on 'this' or 'base' in an instance constructor
//or an 'init' accessor.
Init accessors and readonly fields
init accessors 는 초기화 시에만 호출되기 때문에, enclosing class의 readonly fields 값을 수정할 수 있다.
public class Person
{
private readonly string firstName = "<unknown>";
private readonly string lastName = "<unknown>";
public string FirstName
{
get => firstName;
set => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName))); //ERROR!!
//A readonly field cannot be assigned to
//(except in a constructor or init-only setter of the type
//in which the field is defined or a variable initializer)
}
public string LastName
{
get => lastName;
init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName)));
}
}
Records
classic oop의 core에는 object는 strong identity가 있고, mutable state를 encapsulate한다는 생각이 있다. C#은 기본으로 oop를 지향하기 때문에, object 전체가 immutable하고 value처럼 행동하게 하려면 record를 선언하는 것을 고려해볼 수 있다.
public record Person
{
public string? FirstName { get; init; }
public string? LastName { get; init; }
}
record는 class이지만 record keyword 때문에 몇가지 추가적인 value-like 행동을 한다. records는 identity가 아닌, contents로 판단된다. 이 점에서, records는 structs에 가깝지만, 여전히 reference types이다.
with-expressions
immutable data를 다루는 데, 공통 패턴은 존재하는 object로부터 새로운 value를 만다는 것이다. 예를들어, person의 last name 변경은 변경된 last name과 함께 old one을 복사하는 new object로 나타낼 수 있다. 이 기술을 non-destructive mutation이라 한다. 이 기술을 위해 records는 with-expression 이라는 새로운 종류의 expression을 허용한다.
public class Person
{
public string? FirstName { get; init; }
public string? LastName { get; init; }
}
var person = new Person { FirstName = "Mads", LastName = "Nielsen" };
var otherPerson = person with { LastName = "Torgersen" }; //ERROR!!
//The receiver type 'Person' is not a valid record type and is not a struct type.
public record PersonRecord
{
public string? FirstName { get; init; }
public string? LastName { get; init; }
}
var personRecord = new PersonRecord { FirstName = "Mads", LastName = "Nielsen" };
var otherPersonRecord = personRecord with { LastName = "Torgersen" };
with-expression을 사용하려면 properties는 init 또는 set accessor을 갖고있어야 한다.
Value-based equality
모든 object는 object class로부터 virtual Equals(object) 함수를 상속받는다. value-based로 override하였다.
public record PersonRecord
{
public string? FirstName { get; init; }
public string? LastName { get; init; }
}
var personRecord = new PersonRecord { FirstName = "Mads", LastName = "Nielsen" };
var otherPersonRecord = personRecord with { LastName = "Torgersen" };
var originPersonRecord = otherPersonRecord with { LastName = "Nielsen" };
수정된 object의 last name을 다시 되돌리면, ReferenceEquals(person, originalPerson) = false 이고 Equals(person, originalPerson) = true 이다. value-based Equals과 마찬가지로, value-based GetHashCode() override도 동일하게 동작한다.
Inheritance
Records는 다른 records를 상속받을 수 있다.
public record Person
{
public string? FirstName { get; init; }
public string? LastName { get; init; }
}
public record Student : Person
{
public int ID;
}
Person student = new Student { FirstName = "Mads", LastName = "Nielsen", ID = 129 };
var otherStudent = student with { LastName = "Torgersen" };
WriteLine(otherStudent is Student); // true
Person similarStudent = new Student { FirstName = "Mads", LastName = "Nielsen", ID = 130 };
WriteLine(student != similarStudent); //true, since ID's are different
Positional records
record 사용 시, positional 접근이 유용할 때가 있다. constructor arguments를 통해 contents가 주어진 곳에서 positional deconstruction으로 추출될 수 있다. record에서 your own constructor와 deconstructor를 명시할 수 있다.
public record Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
public Person(string firstName, string lastName)
=> (FirstName, LastName) = (firstName, lastName);
public void Deconstruct(out string firstName, out string lastName)
=> (firstName, lastName) = (FirstName, LastName);
}
동일하지만, 훨씬 간단한 표기방법이 있다.
public record Person(string FirstName, string LastName);
이것은 public init-only auto-properties와 constructor, deconstructor를 선언한다. 따라서 아래와 같이 작성이 가능하다.
var person = new Person("Mads", "Torgersen"); // positional construction
var (f, l) = person; // positional deconstruction
만약에 generated auto-property가 싫으면, 직접 작성할 수 있다. 대신 동일한 이름을 사용해야 한다. 예를들어, FirstName을 protected property로 하려면 아래와 같이 작성한다.
public record Person(string FirstName, string LastName)
{
protected string FirstName { get; init; } = FirstName;
}
3 Different Ways to Implement Value Object in C# 10 (trycatchblog.tech)
'C#' 카테고리의 다른 글
C#] String 검색 함수, 성능 비교 (0) | 2023.10.23 |
---|---|
C#] method signature - generic type, specific type (0) | 2023.09.15 |
C#] Method Parameters - params, out (0) | 2023.08.23 |
C#] Method Parameters - parameters 전달 (0) | 2023.08.22 |
C#] type, System.Type, System.Reflection (0) | 2023.06.19 |
댓글