`init` 키워드

init 키워드 (C# Reference)

객체를 만들 때 딱 한 번만 속성 값을 정하고 싶다면 init 키워드를 쓰면 돼요. init은 프로퍼티나 인덱서의 접근자(accessor) 메서드를 정의하는 키워드인데요, init-only 세터는 객체를 만드는 동안에만 프로퍼티나 인덱서 요소에 값을 할당해 줍니다. 객체가 초기화되고 나면 그 값은 바뀌지 않아요. 이렇게 init는 불변성(immutability)을 강제해서, 한 번 만들어진 객체는 쉽게 변하지 않게 해 주죠.

init 접근자가 있으면 호출하는 쪽 코드에서 개체 이니셜라이저를 써서 초기 값을 설정할 수 있어요. 반대로 get 접근자만 있는 자동 구현 프로퍼티는 생성자를 호출해서 초기화해야 하죠. private set을 가진 프로퍼티는 생성 이후에도 바꿀 수 있지만, 그 클래스 안에서만 가능합니다.

[!INCLUDEcsharp-version-note]

아래 코드는 자동 구현 프로퍼티에서 init 접근자를 쓴 예시예요.

class Person_InitExampleAutoProperty
{
    public int YearOfBirth { get; init; }
}

경우에 따라 접근자 중 하나를 직접 구현해서 매개변수 검증을 해야 할 때가 있어요. 그럴 땐 C# 14에서 도입된 field 키워드를 쓰면 검증을 넣을 수 있습니다. field 키워드는 컴파일러가 합성한 백킹 필드에 접근하게 해 주는데요, 아래 예시는 init 접근자에서 value 매개변수의 범위를 검증하는 프로퍼티를 보여 줍니다.

class Person_InitExampleFieldProperty
{
    public int YearOfBirth
    {
        get;
        init
        {
            field = (value <= DateTime.Now.Year)
                ? value
                : throw new ArgumentOutOfRangeException(nameof(value), "Year of birth can't be in the future");
        }
    }
}

init 접근자는 식 본문 멤버(expression-bodied member)로도 쓸 수 있어요. 예를 들면 이렇게요.

class Person_InitExampleExpressionBodied
{
    private int _yearOfBirth;

    public int YearOfBirth
    {
        get => _yearOfBirth;
        init => _yearOfBirth = value;
    }
}

아래 예시는 YearOfBirth라는 프로퍼티에 getinit 접근자를 모두 정의했어요. 프로퍼티 값을 받쳐 주는 데는 _yearOfBirth라는 private 필드를 사용합니다.

class Person_InitExample
{
     private int _yearOfBirth;

     public int YearOfBirth
     {
         get { return _yearOfBirth; }
         init { _yearOfBirth = value; }
     }
}

init 접근자는 호출하는 쪽이 프로퍼티를 반드시 설정하도록 강제하지 않아요. 대신 개체 이니셜라이저는 쓸 수 있게 하면서 나중에 값을 수정하는 건 막아 주죠. 호출자가 프로퍼티를 꼭 설정하도록 강제하고 싶다면 required 한정자를 붙이면 됩니다. 아래 예시는 백킹 필드로 nullable 값 형식을 쓰는 init-only 프로퍼티예요. 호출자가 YearOfBirth 프로퍼티를 초기화하지 않으면 그 프로퍼티는 기본값인 null을 갖습니다.

class Person_InitExampleNullability
{
    private int? _yearOfBirth;

    public int? YearOfBirth
    {
        get => _yearOfBirth;
        init => _yearOfBirth = value;
    }
}

호출자가 초기 값으로 null이 아닌 값을 꼭 설정하도록 강제하려면, 아래 예시처럼 required 한정자를 추가하면 돼요.

class Person_InitExampleNonNull
{
    private int _yearOfBirth;

    public required int YearOfBirth
    {
        get => _yearOfBirth;
        init => _yearOfBirth = value;
    }
}

마지막 예시는 private set, 읽기 전용(read-only), init 프로퍼티 세 가지가 어떻게 다른지 보여 줍니다. private set 버전과 읽기 전용 버전은 모두 name 속성을 설정하려면 추가한 생성자를 호출해야 해요. private set 버전은 인스턴스가 만들어진 뒤에도 이름을 바꿀 수 있죠. 반면 init 버전은 생성자가 필요 없어요. 호출자는 개체 이니셜라이저로 프로퍼티를 초기화하면 됩니다.

class PersonPrivateSet
{
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public PersonPrivateSet(string first, string last) => (FirstName, LastName) = (first, last);

    public void ChangeName(string first, string last) => (FirstName, LastName) = (first, last);
}

class PersonReadOnly
{
    public string FirstName { get; }
    public string LastName { get; }
    public PersonReadOnly(string first, string last) => (FirstName, LastName) = (first, last);
}

class PersonInit
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}
PersonPrivateSet personPrivateSet = new("Bill", "Gates");
PersonReadOnly personReadOnly = new("Bill", "Gates");
PersonInit personInit = new() { FirstName = "Bill", LastName = "Gates" };

더 알아보기

출처: The init keyword (C# Reference)