C# 7.2 - readonly 구조체
C# 7.2 (1) - readonly 구조체
; https://www.sysnet.pe.kr/2/0/11524
C# 7.2 (2) - 메서드의 매개 변수에 in 변경자 추가
; https://www.sysnet.pe.kr/2/0/11525
C# 7.2 (3) - 메서드의 반환값 및 로컬 변수에 ref readonly 기능 추가
; https://www.sysnet.pe.kr/2/0/11526
C# 7.2 (4) - 3항 연산자에 ref 지원(conditional ref operator)
; https://www.sysnet.pe.kr/2/0/11528
C# 7.2 (5) - 스택에만 생성할 수 있는 값 타입 지원 - "ref struct"
; https://www.sysnet.pe.kr/2/0/11530
C# 7.2 (6) - Span<T>
; https://www.sysnet.pe.kr/2/0/11534
C# 7.2 (7) - private protected 접근자 추가
; https://www.sysnet.pe.kr/2/0/11543
C# 7.2 (8) - 숫자 리터럴의 선행 밑줄과 뒤에 오지 않는 명명된 인수
; https://www.sysnet.pe.kr/2/0/11544
기타 - Microsoft Build 2018 - The future of C# 동영상 내용 정리
; https://www.sysnet.pe.kr/2/0/11536
지난 글에서 readonly 필드에 대해 알아봤습니다.
C# - 값 형식의 readonly 인스턴스에 대한 메서드 호출 시 defensive copy 발생
; https://www.sysnet.pe.kr/2/0/11523
값 타입(struct) readonly 인스턴스의 경우 "defensive copy"와 같은 C# 컴파일러의 노력을 통해 값을 변경할 수 없게 만들었다는 것을 설명했는데요. 반면, 지난번 예제에서 봤듯이 "defensive copy"는 의도치 않은 부작용을 낳습니다. 그 이외에도 "defensive copy"는 숨겨진 코드로 인한 값 복사로 메서드 및 공용 속성을 접근할 때 성능 저하가 발생하는 문제도 있습니다.
C# 7.2부터 이런 문제를 아예 미연에 방지할 수 있도록 readonly 구조체(struct)를 정의할 수 있게 합니다. 예를 들기 위해 지난번 예제 코드에서,
class Program
{
readonly StructPerson sarah = new StructPerson() { Name = "Kerrigan", Age = 27 };
static void Main(string[] args)
{
Program pg = new Program();
pg.Test();
}
private void Test()
{
sarah.IncAge();
}
}
struct StructPerson
{
public int Age;
public string Name;
public void IncAge()
{
Age++;
}
}
StructPerson 구조체에 readonly를 부여해 보겠습니다.
readonly struct StructPerson
{
public int Age; // 컴파일 에러: CS8340 Instance fields of readonly structs must be readonly.
public string Name; // 컴파일 에러: CS8340 Instance fields of readonly structs must be readonly.
public void IncAge()
{
Age++;
}
}
보는 바와 같이 struct에 readonly를 부여하면 내부의 모든 필드에 readonly를 부여해야 한다고 강제력을 갖게 되므로 다음과 같이 고쳐야 합니다.
readonly struct StructPerson
{
public readonly int Age;
public readonly string Name;
public void IncAge()
{
Age++;
}
}
이 때문에, 해당 필드들은 반드시 생성자 내에서만 초기화될 수 있고, IncAge와 같은 메서드 내에서는 자연스럽게 값을 변경할 수 없게 됩니다. 따라서 코드는 다시 다음과 같이 바뀌어야 합니다.
readonly struct StructPerson
{
public readonly int Age;
public readonly string Name;
public StructPerson(string name, int age)
{
Name = name;
Age = age;
}
public StructPerson IncAge()
{
return new StructPerson(this.Name, this.Age + 1);
}
}
오호... 위의 코드를 보니 뭔가 연상되지 않나요? 그렇습니다. readonly 구조체는 부작용 없는 불변(immutable) 객체를 만들기 위한 강제성을 부여할 수 있는 방법입니다. 자, 그럼
지난번 코드에서 readonly 필드인 경우 "defensive copy"가 발생했던 호출 측의 코드를 보겠습니다.
class Program
{
readonly StructPerson sarah = new StructPerson("Kerrigan", 27);
static void Main(string[] args)
{
Program pg = new Program();
pg.Test();
}
private void Test()
{
sarah.IncAge();
/*
일반 구조체의 경우 IncAge 메서드 호출은 다음과 같은 코드로 바뀌지만,
StructPerson temp = sarah;
temp.IncAge();
*/
}
}
이제 C# 컴파일러는 Test 메서드 내에서 호출한 IncAge에 대해 더 이상 "defensive copy" 코드를 생성하지 않습니다. 왜냐하면 해당 구조체의 모든 메서드 및 공용 속성은 내부 상태 값을 변경할 수 없다는 것이 보장되었기 때문에 굳이 상태 보존을 위한 값 복사 코드를 넣을 필요가 없는 것입니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
readonly 구조체를 설명하기 위해 지난 글에서 "defensive copy"까지 설명했어야 하는데요. 정리해 보면, 굳이 그런 어려운 용어를 기억할 필요 없이 다음과 같이만 알아두면 됩니다.
C# 7.2부터, 불변(immutable) 객체를 만들 때는 readonly 구조체를 사용한다!
또한 대개의 경우 struct는 불변 객체로 만들지 않을 이유가 없으므로 가능한 struct는 모두 readonly 구조체로 만든다!
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]