required 한정자
required 한정자 (C# 참조)
객체를 만들 때 특정 필드나 프로퍼티의 초기화를 강제하고 싶을 때가 있어요. 생성자만으로는 유연하게 쓰기 어려울 때가 많죠. required 한정자는 그런 상황에서 객체 초기화 시 반드시 값을 넣도록 요구하는 역할을 합니다. 이 한정자가 붙은 필드 또는 프로퍼티는 객체 이니셜라이저로 반드시 초기화해야 해요. 해당 타입의 새 인스턴스를 만드는 어떤 표현식이든 모든 required 멤버를 초기화해야 합니다. required 한정자는 C# 11부터 사용할 수 있어요.
본문
required 한정자를 쓰면 프로퍼티나 필드가 제대로 초기화돼야 하는 타입을 만들 수 있으면서도, 여전히 객체 이니셜라이저를 통한 초기화를 허용할 수 있어요. 이 동작을 보장하는 규칙이 몇 가지 있습니다.
struct와class타입(여기에는record,record struct타입도 포함돼요)에 선언된 필드와 프로퍼티에required한정자를 적용할 수 있어요. 하지만interface의 멤버에는 적용할 수 없습니다.- 명시적 인터페이스 구현은
required로 표시할 수 없어요. 객체 이니셜라이저에서도 설정할 수 없습니다. - required 멤버는 반드시 초기화해야 하지만,
null로 초기화하는 것도 가능해요. 만약 타입이 null 허용되지 않는 참조 타입이라면 멤버를null로 초기화할 때 컴파일러가 경고를 냅니다. 멤버를 아예 초기화하지 않으면 컴파일러가 오류를 냅니다. - required 멤버는 자신을 담고 있는 타입 이상으로 공개돼야 해요. 예를 들어
public클래스는protected인required필드를 가질 수 없습니다. 또 required 프로퍼티는 자신을 담는 타입 이상으로 공개된 setter(set또는init접근자)를 가져야 해요. 인스턴스를 만드는 코드는 접근할 수 없는 멤버를 설정할 수 없으니까요. - 파생 클래스는 기본 클래스에 선언된
required멤버를 숨길 수 없습니다. required 멤버를 숨기면 호출자가 객체 이니셜라이저로 그 멤버를 쓸 수 없게 되거든요. 게다가 required 프로퍼티를 재정의(override)하는 파생 타입은 반드시required한정자를 함께 포함해야 해요. 파생 타입은 required 상태를 제거할 수는 없지만, 프로퍼티를 재정의할 때required한정자를 새로 추가할 수는 있습니다. - 타입 매개변수에
new()제약이 있을 때,required멤버가 있는 타입은 그 타입 인자로 사용할 수 없습니다. 제네릭 코드에서는 모든 required 멤버가 초기화된다는 걸 컴파일러가 강제할 수 없기 때문이에요. - record의 위치 매개변수(positional parameter) 선언에는
required한정자를 사용할 수 없습니다. 다만required한정자를 포함하는 위치 프로퍼티를 명시적으로 선언하는 것은 가능해요.
위치 record처럼 기본 생성자(primary constructor)로 위치 프로퍼티를 초기화하는 타입도 있죠. 그 프로퍼티 중 하나라도 required 한정자를 포함하면, 기본 생성자에 SetsRequiredMembers 특성이 추가됩니다. 이 특성은 기본 생성자가 모든 required 멤버를 초기화한다는 뜻을 나타내요. 직접 만든 생성자에 xref:System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute?displayProperty=nameWithType 특성을 붙일 수도 있습니다. 다만 컴파일러가 이런 생성자가 모든 required 멤버를 실제로 초기화하는지 검증하지는 않아요. 이 특성은 그저 생성자가 모든 required 멤버를 초기화한다고 컴파일러에게 단언하는 역할을 합니다. SetsRequiredMembers 특성은 생성자에 다음 규칙을 더합니다.
this()또는base()로SetsRequiredMembers특성이 달린 다른 생성자에 연결되는 생성자도 반드시SetsRequiredMembers특성을 포함해야 해요. 그래야 호출자가 모든 적절한 생성자를 올바르게 사용할 수 있습니다.record타입용으로 생성되는 복사 생성자(copy constructor)는 멤버 중 하나라도required라면SetsRequiredMembers특성이 적용됩니다.
[!WARNING]
SetsRequiredMembers특성은 객체가 생성될 때 모든required멤버가 초기화됐는지 확인하는 컴파일러 검사를 꺼버립니다. 조심해서 사용하세요.
아래 코드는 FirstName과 LastName 프로퍼티에 required 한정자를 쓴 클래스 계층을 보여줘요.
using System.Diagnostics.CodeAnalysis;
namespace RequiredMembers;
public class Person
{
public Person() { }
[SetsRequiredMembers]
public Person(string firstName, string lastName) =>
(FirstName, LastName) = (firstName, lastName);
public required string FirstName { get; init; }
public required string LastName { get; init; }
public int? Age { get; set; }
}
public class Student : Person
{
public Student() : base()
{
}
[SetsRequiredMembers]
public Student(string firstName, string lastName) :
base(firstName, lastName)
{
}
public double GPA { get; set; }
}
더 알아보기
- C# 언어 사양의 Properties 섹션에서 required 멤버에 대한 더 자세한 내용을 볼 수 있어요.
- C# 언어 사양의 Required member attributes 섹션도 함께 살펴보면 좋습니다.