본문 바로가기
프로그래밍/Effective C#

Effective C# Item 13 : 정적 클래스 멤버를 올바르게 초기화하라

by bantomak 2023. 10. 27.

정적 클래스 멤버를 올바르게 초기화하라.

정적 멤버 변수를 포함하는 타입이 있다면 인스턴스를 생성하기 전에 반드시 정적 멤버 변수를 초기화해야 한다. 이를 위해

 

C#에서는

  • 정적 멤버 초기화 구문(static member initializer)
  • 정적 생성자(static constructors)

라는 두 가지 기능을 제공한다.

 

 

정적 생성자는 타입 내에 정의된 모든 메서드, 변수, 속성에 최초로 접근하기 전에 자동으로 호출되는 특이한 메서드다. 이 메서드를 활용하면 정적 변수를 초기화하거나, 싱글톤 패턴을 적용하거나, 혹은 여타의 작업을 효과적으로 수행할 수 있다. 정적 변수를 초기화하기 위해서 인스턴스 생성자나 전용의 private:메서드 혹은 다른 관용구를 사용해서는 안된다. 정적 필드를 초기화하는 과정이 매우 복잡하거나 혹은 상당한 자원을 소비하는 경우라면 Lazy<T>를 사용하여 해당 빌드에 최초로 접근하는 시점까지 초기화 작업을 미룰 수 있다.

 

인스턴스 멤버 초기화와 마찬가지로 정적 멤버를 간단히 초기화하는 경우라면 정적 생성자를 사용하기보다는 정적 멤버 초기화 구문을 사용하는 것이 좋다. 하지만 초기화 과정이 복잡하다면 정적 생성자를 사용하는 것도 나쁘지 않다. C#에서 정적 생성자를 사용하는 대표적인 사례 중 하나가 싱글톤(Singleton) 패턴을 구현하는 경우다. 간단하게는 다음과 같이 인스턴스 생성자를 private 선언하고 멤버 초기화 구문을 사용하면 된다.

 

public class MySingleton
{
   private static readonly MySingleton theOneAndOnly = new MySingleton();
   
   public static MySingleton TheOnly
   {
      get 
      {
         return theOneAndOnly;
      }
   }
   
   private MySingleton()
   {
   }
   
   // 이하 생략
}

 

초기화 과정이 더 복잡한 경우라면 다음과 같이 정적 생성자를 사용하는 것도 좋은 방법이다.

 

public class MySingleton2
{
   private static readonly MySingleton2 theOneAndOnly = new MySingleton();
   
   static MySingleton2()
   {
      theOneAndOnly = new Singleton2();
   }
   
   public static MySingleton2 TheOnly
   {
      get
      {
         return theOneAndOnly;
      }
   }
   
   private MySingleton()
   {
   }
   
   // 이하 생략
}

 

인스턴스 멤버 초기화 구문과 마찬가지로 정적 멤버 초기화 구문 또한 정적 생성자가 호출되기 이전에 실행되며, 베이스 클래스의 정적 생성자보다도 먼저 호출된다.

 

앱도메인(AppDomain) 내에서 CLR이 특정 타입에 접근해야 하는 경우 정적 생성자를 우선적으로 호출한다. 모든 타입은 정적 생성자를 하나만 가질 수 있으며 어떠한 인자도 넘길 수 없다. 정적 생성자는 CLR에 의해서 호출되기 때문에 예외가 발생할 가능성이 있는 경우라면 매우 조심스럽게 다뤄야 한다. 정적 생성자 내에서 예외가 발생하면 CLR은 TypeInitializtionException을 던지고 응용프로그램을 종료해 버린다. 만약 호출하는 쪽에서 이 예외를 잡아버리면(catch) 상황은 더욱 복잡해진다. 이 경우 해당 타입이 포함된 AppDomain을 언로드 하지 않은 한 이 타입으로 객체를 생성하지 못한다. CLR이 정적 생성자를 호출하긴 하였지만 해당 타입을 제대로 초기화하지는 못했다고 간주하기 때문이다.

 

이러한 타입 초기화 과정은 재시도되지 않으며, 타입이 올바르게 초기화되지 않았으므로 이 타입을 이용하여(혹은 이 타입을 상속한 파생 타입을 이용하여) 객체를 생성하는 등의 작업 또한 허용되지 않는다.

 

멤버 초기화 구문 대신 반드시 정적 생성자를 사용해야 할 때도 있다. 바로 예외가 발생할 가능성이 있는 경우인데, 멤버 초기화 구문의 경우 예외를 잡아낼 방법이 없기 때문이다. 정적 생성자를 이용하면 다음과 같은 코드를 작성할 수 있다.

(아이템 47: 사용자 지정 예외 클래스를 완벽하게 작성하라 참조)

 

public class MySingleton2
{
   try
   {
      theOneAndOnly = new Singleton2();
   }
   catch
   {
      // 복구를 시도한다.
   }
}

 

정적 멤버 초기화 구문과 정적 생성자는 클래스의 정적 멤버를 초기화하는 깔끔하고 명확한 방법을 제공한다. 이를 활용하면 코드를 읽기 쉽고 올바르게 작성할 수 있다. 이 기능은 다른 언어에서 정적 멤버를 초기화할 때 겪는 어려움을 언어 차원에서 해결해 주기 위해서 추가되었다.

 

출처

 

이펙티브 C# - 예스24

더 나은 C# 코드를 작성하는 새로운 방법 50가지 C#은 전통적인 .NET 기반 개발에서 유니티 게임 엔진으로 개발 영역을 확대하면서 더욱 주목받고 있다. 또한 자마린으로 다양한 모바일 플랫폼에

댓글