본 글은 NDepend의 Patrick Smacchia님의 C#12 class and struct Primary Constructors를 번역하였습니다.
C# 9부터는 class record
(또는 그냥 record
)와 struct record
편리한 기본 생성자 구문이 있습니다.
var p = new Person("Seth", "Gecko");
Assert.IsTrue($"{p.FirstName} {p.LastName}" == "Seth Gecko");
// p.FirstName = "Alan"; ERROR record are immutable by default
public record Person(string FirstName, string LastName);
C# 12는 비 레코드인 class
와 struct
에 대한 기본 생성자를 도입했지만 매우 다르다는 점에 유의하세요! 근본적인 동기가 다르기 때문입니다.
record
기본 생성자는 공용 읽기 전용 프로퍼티를 생성하는 간결한 방법을 나타냅니다. 이는 레코드가 일부 상태를 보유하도록 설계된 단순한 불변 객체이기 때문입니다.class
및struct
기본 생성자는 비공개 필드를 생성하는 간결한 방법을 나타냅니다.class
와struct
는 생성 시 초기화되는 내부 상태를 사용하는 내부 로직이 있는 구현이기 때문입니다.
예를 들어 여기에는 꽤 많은 코드 줄을 절약할 수 있는 상황이 있습니다.
record
기본 생성자와 달리 속성은 생성되지 않는다는 점을 분명히 해두겠습니다.
생성된 비공개 필드를 어떻게 사용할 수 있는지 설명해 보겠습니다.
var p = new Person(1970);
Assert.IsTrue(p.YearOfBirth1 == 1970);
p.YearOfBirth1 = 1975; // Modify the property backing field
Assert.IsTrue(p.YearOfBirth1 == 1975);
Assert.IsTrue(p.YearOfBirth2 == 1970);
p.YearOfBirth2 = 1968; // Modify the field generated by the primay constructor
Assert.IsTrue(p.YearOfBirth2 == 1968);
public class Person(int yearOfBirth) {
// Assign the primary constructor value of yearOfBirth to the property backing field
public int YearOfBirth1 { get; set; } = yearOfBirth;
// Read and assign the field generated for yearOfBirth
public int YearOfBirth2 { get { return yearOfBirth; } set { yearOfBirth = value; } }
}
실제로 위의 코드 샘플에서 C# 컴파일러는 2개의 비공개 필드를 생성하는데, 하나는 기본 생성자 매개변수 yearOfBirth
에 대한 것이고 다른 하나는 속성 YearOfBirth1
백킹 필드에 대한 것입니다.
평소와 마찬가지로 이번 사례와 같은 이상한 상황을 초래할 수 있는 네이밍 충돌은 피하는 것이 좋습니다.
속성이 생성되지 않으므로 record
기본 생성자와 또 다른 차이점은 with
키워드를 사용할 수 없다는 점입니다.
기본 생성자 및 기타 생성자
class
와 struct
기본 생성자를 사용하면 코드를 상당히 절약할 수 있지만, 단점은 (최종적으로) 다른 생성자가 this
키워드를 통해 기본 생성자를 호출하도록 강제한다는 것입니다.
var p1 = new Person("Seth", "Gecko");
Assert.IsTrue(p1.FullName == "Seth Gecko");
var p2 = new Person("Seth");
Assert.IsTrue(p2.FullName == "Seth Smith");
public class Person(string firstName, string lastName) {
public Person(string firstName) : this(firstName, "Smith") { }
public string FullName => $"{firstName} {lastName}";
}
또한 기본 클래스에 매개 변수가 없는 생성자(기본 생성자라고 함)가 없는 한 파생 클래스는 기본 생성자를 가질 수 없습니다.
마지막으로 기본 생성자가 있는 struct
에도 여전히 기본 생성자가 있다는 것을 알아봅시다.
var p1 = new PersonStruct();
Assert.IsTrue(p1.Name == null);
Assert.IsTrue(p1.YearOfBirth == 0);
var p2 = default(PersonStruct);
Assert.IsTrue(p2.Name == null);
Assert.IsTrue(p2.YearOfBirth == 0);
public struct PersonStruct(string name, int yearOfBirth) {
public string Name { get; } = name;
public int YearOfBirth { get; } = yearOfBirth;
}
이는 C#에서 struct
가 값 형 의미를 갖기 때문입니다. 즉, 모든 상태가 기본값으로 초기화되고, 값은 0
, 참조는 null
로 초기화되는 new TStruct()
를 통해 default(TStruct)
인스턴스를 생성할 수 있습니다.
결론
C# 12 class
및 struct
기본 생성자는 보다 간결한 코드를 작성할 수 있는 훌륭한 새 구문입니다. 하지만 기존의 record
기본 생성자 구문과는 상당히 다르기 때문에 오해의 소지가 있을 수 있습니다. 이 글에서 다른 근본적인 동기를 강조한 것도 바로 이 때문입니다.
class
및 struct
기본 생성자에 중단점을 설정할 수 있었으면 합니다. 지금은 매개 변수가 속성 초기화에 사용될 때만 기본 생성자 호출을 중단할 수 있습니다. (record
기본 생성자에도 그런 기능이 없는 것으로 확인했습니다.) 이 기능은 get;
또는 set;
표현식을 중단하는 것과 같은 방식으로 유용할 수 있습니다. 이에 대한 요청을 제출했습니다.