← 문제로

30. 정적 생성자와 타입 초기화 타이밍(beforefieldinit)

난이도 중
내 리뷰 · C#
해설 · C#

해설 — 정적 생성자와 타입 초기화 타이밍(beforefieldinit)

난이도: 중상

예측 출력

start
Config.Load
42
Counter..cctor
101
102
--- mutual ---
2
10

핵심 개념

  • 타입 초기화자(type initializer, .cctor): 정적 필드 이니셜라이저와 명시적 static 생성자는 컴파일 시 하나의 .cctor 로 합쳐져 CLR이 타입당 한 번 자동 호출한다.
  • beforefieldinit 플래그: 타입에 명시적 정적 생성자가 없고 정적 필드 이니셜라이저만 있으면 컴파일러가 beforefieldinit 를 붙인다. 이 경우 CLR은 "그 타입의 정적 필드에 처음 접근하기 전 어느 시점"에 초기화하면 되며, 정확한 시점은 구현에 맡겨진다(더 일찍 앞당길 수 있음).
  • 명시적 정적 생성자가 있으면 beforefieldinit 이 없어 초기화가 최초 사용 직전으로 정확히 지연된다. 즉 부수효과 타이밍이 결정적이다.

단계별 내부 동작 (출력 근거)

  1. "start".
  2. Config.Value 접근 → Config 의 .cctor 실행: Load()Config.Load 출력, Value=42. 이어서 42.
  3. Counter.Next() 최초 호출 → Counter 의 .cctor 실행(Counter..cctor, _n=100). ++_n101.
  4. 두 번째 Counter.Next() → cctor 재실행 안 함(타입당 1회) → 102.
  5. --- mutual ---.
  6. Mutual.A 접근 → Mutual 초기화 시작. .cctor 는 필드를 소스 순서로 초기화한다(A 먼저, B 나중).
    • A = ComputeA() = Mutual2.Bv + 1.
      • Mutual2.Bv 접근 → Mutual2 초기화 시작. 소스 순서로 Bv 먼저:
        • Bv = ComputeB() = Mutual.B + 1.
          • Mutual.B 를 읽는데 Mutual 은 아직 초기화 중(A 이니셜라이저 실행 도중이며 B 는 아직 미실행). 같은 스레드의 재귀적 초기화이므로 CLR은 다시 돌리지 않고 부분 초기화 상태를 보게 한다 → Mutual.B == 0(기본값). 따라서 ComputeB() = 0 + 1 = 1, Mutual2.Bv = 1.
        • 이어서 Av = 5. Mutual2 초기화 완료.
      • 돌아와서 ComputeA() = Mutual2.Bv(1) + 1 = 2. 즉 Mutual.A = 2.
    • 다음 필드 B = 10. Mutual 초기화 완료(A=2, B=10).
    • 출력 2.
  7. Mutual.B → 이미 초기화됨 → 10.

흔한 오해·함정

  • "정적 생성자는 프로그램 시작 시 실행된다" — 틀림. 명시적 cctor는 최초 사용 직전 지연 실행되고, beforefieldinit 타입은 더 일찍 앞당겨질 수 있어 부수효과 타이밍을 신뢰하면 안 된다.
  • 빈 정적 생성자를 굳이 넣으면 beforefieldinit 이 사라져 지연 초기화가 강제되고, 정적 필드 접근마다 초기화 여부 체크 비용이 붙어 핫패스에서 성능에 불리할 수 있다.
  • 순환 정적 초기화는 위 6번처럼 부분 초기값(0/null)을 조용히 관측하게 만든다. 값이 접근 순서에 의존해 재현이 어려운 버그가 된다 — 정적 초기화 사이의 상호 의존을 피하라.
  • 타입 초기화 중 예외가 나면 TypeInitializationException 으로 감싸지고, 그 타입은 해당 프로세스에서 사실상 사용 불가가 된다.

면접 포인트

  • beforefieldinit 이 무엇이고 왜 타이밍/성능에 영향을 주는가. 빈 static ctor 의 부작용.
  • 타입 초기화가 "정확히 언제" 트리거되는가(정적 멤버 접근·인스턴스 생성·정적 메서드 호출 직전).
  • 순환 정적 초기화가 부분 초기값을 노출하는 메커니즘과, 그 값이 접근 순서에 의존하는 이유.