required 한정자

required 한정자 (C# 참조)

객체를 만들 때 특정 필드나 프로퍼티의 초기화를 강제하고 싶을 때가 있어요. 생성자만으로는 유연하게 쓰기 어려울 때가 많죠. required 한정자는 그런 상황에서 객체 초기화 시 반드시 값을 넣도록 요구하는 역할을 합니다. 이 한정자가 붙은 필드 또는 프로퍼티객체 이니셜라이저로 반드시 초기화해야 해요. 해당 타입의 새 인스턴스를 만드는 어떤 표현식이든 모든 required 멤버를 초기화해야 합니다. required 한정자는 C# 11부터 사용할 수 있어요.

출처: required modifier - C# Reference

본문

required 한정자를 쓰면 프로퍼티나 필드가 제대로 초기화돼야 하는 타입을 만들 수 있으면서도, 여전히 객체 이니셜라이저를 통한 초기화를 허용할 수 있어요. 이 동작을 보장하는 규칙이 몇 가지 있습니다.

  • structclass 타입(여기에는 record, record struct 타입도 포함돼요)에 선언된 필드프로퍼티required 한정자를 적용할 수 있어요. 하지만 interface의 멤버에는 적용할 수 없습니다.
  • 명시적 인터페이스 구현은 required로 표시할 수 없어요. 객체 이니셜라이저에서도 설정할 수 없습니다.
  • required 멤버는 반드시 초기화해야 하지만, null로 초기화하는 것도 가능해요. 만약 타입이 null 허용되지 않는 참조 타입이라면 멤버를 null로 초기화할 때 컴파일러가 경고를 냅니다. 멤버를 아예 초기화하지 않으면 컴파일러가 오류를 냅니다.
  • required 멤버는 자신을 담고 있는 타입 이상으로 공개돼야 해요. 예를 들어 public 클래스는 protectedrequired 필드를 가질 수 없습니다. 또 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 멤버가 초기화됐는지 확인하는 컴파일러 검사를 꺼버립니다. 조심해서 사용하세요.

아래 코드는 FirstNameLastName 프로퍼티에 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; }
}

더 알아보기