`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라는 프로퍼티에 get과 init 접근자를 모두 정의했어요. 프로퍼티 값을 받쳐 주는 데는 _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" };