C#12 클래스 및 구조체 기본 생성자 | Patrick Smacchia

본 글은 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는 비 레코드인 classstruct에 대한 기본 생성자를 도입했지만 매우 다르다는 점에 유의하세요! 근본적인 동기가 다르기 때문입니다.

  • record 기본 생성자는 공용 읽기 전용 프로퍼티를 생성하는 간결한 방법을 나타냅니다. 이는 레코드가 일부 상태를 보유하도록 설계된 단순한 불변 객체이기 때문입니다.
  • classstruct 기본 생성자는 비공개 필드를 생성하는 간결한 방법을 나타냅니다. classstruct는 생성 시 초기화되는 내부 상태를 사용하는 내부 로직이 있는 구현이기 때문입니다.

예를 들어 여기에는 꽤 많은 코드 줄을 절약할 수 있는 상황이 있습니다.

image

record 기본 생성자와 달리 속성은 생성되지 않는다는 점을 분명히 해두겠습니다.

image

생성된 비공개 필드를 어떻게 사용할 수 있는지 설명해 보겠습니다.

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 백킹 필드에 대한 것입니다.

image

평소와 마찬가지로 이번 사례와 같은 이상한 상황을 초래할 수 있는 네이밍 충돌은 피하는 것이 좋습니다.

image

속성이 생성되지 않으므로 record 기본 생성자와 또 다른 차이점은 with 키워드를 사용할 수 없다는 점입니다.

image

기본 생성자 및 기타 생성자

classstruct 기본 생성자를 사용하면 코드를 상당히 절약할 수 있지만, 단점은 (최종적으로) 다른 생성자가 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}";
}

또한 기본 클래스에 매개 변수가 없는 생성자(기본 생성자라고 함)가 없는 한 파생 클래스는 기본 생성자를 가질 수 없습니다.

image

마지막으로 기본 생성자가 있는 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 classstruct 기본 생성자는 보다 간결한 코드를 작성할 수 있는 훌륭한 새 구문입니다. 하지만 기존의 record 기본 생성자 구문과는 상당히 다르기 때문에 오해의 소지가 있을 수 있습니다. 이 글에서 다른 근본적인 동기를 강조한 것도 바로 이 때문입니다.


classstruct 기본 생성자에 중단점을 설정할 수 있었으면 합니다. 지금은 매개 변수가 속성 초기화에 사용될 때만 기본 생성자 호출을 중단할 수 있습니다. (record 기본 생성자에도 그런 기능이 없는 것으로 확인했습니다.) 이 기능은 get; 또는 set; 표현식을 중단하는 것과 같은 방식으로 유용할 수 있습니다. 이에 대한 요청을 제출했습니다.

image


3개의 좋아요