16. readonly struct · in 매개변수 · 방어적 복사(defensive copy)
난이도 상내 리뷰 · C#
해설 · C#
해설 — readonly struct · in 매개변수 · 방어적 복사(defensive copy)
난이도: 상
요약
가변 구조체의 비 readonly 메서드를 readonly 컨텍스트(readonly 필드, in 매개변수, ref readonly)에서 호출하면, 컴파일러가 "이 메서드가 상태를 바꿀 수 있다"고 가정해 호출 전에 구조체를 복사하고 그 복사본에서 메서드를 실행한다. 그래서 변경이 원본에 반영되지 않고(정확성), 매 호출마다 복사가 생긴다(성능).
예측 출력
a=11 b=11 field=10
n1=101 n2=101 local=100
d1=1 d2=2 direct=2
핵심 개념 · 단계별 내부 동작
- (1) readonly 필드:
s_counter는static readonly.Next()는Value++를 하는 비 readonly 메서드라 readonly 위치에서 직접 호출하면 readonly 위반이 된다. 컴파일러는 이를 막는 대신s_counter를 임시 복사한 뒤 복사본에서Next()를 호출한다.a: 복사본 10→11, 반환 11. 원본은 그대로 10.b: 다시 원본(여전히 10) 에서 복사 → 10→11, 반환 11.field:s_counter.Value= 10(한 번도 안 바뀜). →a=11 b=11 field=10.
- (2) in 매개변수:
in Counter c는 읽기 전용 참조다.c.Next()호출 시 같은 이유로 매 호출 방어적 복사.local.Value=100은 그대로, n1=n2=101. - (3) 직접 호출:
direct는 평범한 가변 지역 변수 → 복사 없이 제자리 변경. d1=1, d2=2, direct=2.
흔한 오해·함정
- "in 으로 넘기면 빨라진다"는 절반만 맞다: 큰 구조체를 복사 없이 참조로 넘기려고
in을 쓰지만, 그 안에서 비 readonly 멤버(메서드·프로퍼티 getter 포함) 를 호출하면 오히려 매번 방어적 복사가 생겨 더 느려질 수 있다. - 프로퍼티 getter 도 메서드다:
readonly가 아닌 getter 는 컴파일러가 변경 가능으로 보아 복사를 유발한다. - 변경이 조용히 사라짐: 가변 구조체를 readonly 필드/
in으로 다루면 메서드의 상태 변경이 원본에 안 남아 디버깅이 어렵다(csharp_internals/problem3가변 구조체 함정과 한 묶음).
수정안(복사 제거)
- 구조체를
readonly struct로: 모든 멤버가 불변임을 컴파일러가 알면 방어적 복사가 사라진다. - 변경 없는 멤버에
readonly한정자:public readonly int Peek() => Value;처럼 메서드/프로퍼티에readonly를 붙이면 그 호출엔 복사가 안 생긴다. - 가변 의미가 필요하면
ref: 진짜로 제자리 변경을 원하면in이 아니라ref Counter로 넘긴다. - 큰 데이터는 class 고려: 값 의미가 꼭 필요 없으면 참조 타입이 복사 문제를 원천 제거.
면접 포인트
- 방어적 복사가 끼어드는 정확한 조건(readonly 필드/
in/ref readonly위에서 비 readonly 멤버 호출)과 그 비용. readonly struct/readonly멤버가 어떻게 복사를 제거하는가, 프로퍼티 getter 도 복사 유발 대상인 점.in매개변수 최적화의 함정 — 큰 구조체를 넘겨도 비 readonly 멤버 접근이 있으면 이득이 사라진다.