
정적 멤버를 다루다 보면 이런 궁금증이 생길 수 있습니다.
"인스턴스 멤버는 생성자로 초기화하는데, 정적 멤버는 어떻게 초기화하나요?"
"클래스의 모든 멤버를 정적으로만 가지고 있다면, 다룰 방법이 없을까요?"
이 질문들에 대한 해답은 '정적 생성자'와 '정적 클래스'에 있습니다.
인스턴스 생성자가 객체를 만들 때마다 호출되어 각 객체를 초기화한다면,
정적 생성자는 해당 클래스가 프로그램에서 처음 사용되는 순간,
CLR에 의해 단 한 번만 호출되어 클래스 자체를 초기화합니다.
[특징]
1. static키워드를 붙여서 만듭니다.
2. 접근 제한자(public, private 등)를 사용할 수 없습니다.
3. 매개변수(parameter)를 가질 수 없습니다.
4. 클래스당 단 하나만 존재할 수 있습니다.
5. CLR에 의해 자동으로 한 번만 호출합니다.
6. 정적 생성자는 코드에서 직접 호출할 수 없습니다.
[코드]
using System;
// 각종 설정 값을 파일에서 딱 한 번만 읽어와야 하는 상황을 가정해 봅시다.
public class GameConfig
{
// 모든 객체가 공유할 정적 필드
public static int MaxPlayers { get; private set; }
public static string Difficulty { get; private set; }
// 정적 생성자: 이 클래스가 처음 사용될 때 단 한 번 실행됩니다.
static GameConfig()
{
// 설정 파일의 값을 읽어온다고 가정합시다.
Console.WriteLine("정적 생성자 호출! 설정 파일을 읽어옵니다...");
MaxPlayers = 100;
Difficulty = "Hard";
Console.WriteLine("설정 완료!");
}
// 인스턴스 생성자
public GameConfig()
{
Console.WriteLine("인스턴스 생성자 호출!");
}
}
class Program
{
static void Main()
{
Console.WriteLine("프로그램 시작!");
// 1. 정적 멤버에 처음 접근하는 순간, 정적 생성자가 호출됩니다.
Console.WriteLine($"현재 난이도: {GameConfig.Difficulty}");
Console.WriteLine("---");
// 2. 인스턴스를 생성해 봅니다.
GameConfig config1 = new GameConfig();
GameConfig config2 = new GameConfig();
// 정적 생성자는 더 이상 호출되지 않습니다.
Console.WriteLine($"최대 플레이어 수: {GameConfig.MaxPlayers}");
}
}
[실행 결과]
프로그램 시작!
정적 생성자 호출! 설정 파일을 읽어옵니다...
설정 완료!
현재 난이도: Hard
---
인스턴스 생성자 호출!
인스턴스 생성자 호출!
최대 플레이어 수: 100
정적 생성자는 맨 처음에 한 번만 호출된 것을 확인할 수 있습니다.
주의 사항
정적 생성자 안에서 너무 무거운 작업이나, 실패 가능성이 큰 작업을 수행하는 것은
프로그램 시작 시 느려지거나, 타입 전체가 사용 불가능해질 수 있습니다.
이러한 이유로, 정적 생성자는 가볍고, 실패하지 않는 작업에서 사용해야 합니다.
정적 클래스는 모든 멤버가 static으로만 이루어진 클래스를 말합니다.
이렇게 하면 "이 클래스는 순수한 기능 모음집입니다."라는 의도를
코드에 명확하게 표현할 수 있습니다.
[특징]
1. static키워드를 클래스 선언에 붙여야 합니다.
2. 오직 정적 멤버(필드, 메서드 등)만 가질 수 있습니다.
3. 인스턴스를 생성할 수 없습니다. (new키워드 사용 불가)
4. 정적 생성자를 제외한 생성자를 가질 수 없습니다.
5. 다른 클래스를 상속받거나, 다른 클래스에게 상속을 해줄 수 없습니다.
유용한 기능(메서드)이나 상수(const)들을 모아놓은 공용 도구 상자입니다.
물건 자체를 새로 만들(new) 필요 없고, 누구나 사용할 수 있어요.
[코드]
using System;
// static 키워드가 붙은 정적 클래스
public static class GameHelper
{
public const float Pi = 3.14159f;
// 두 점 사이의 거리를 계산하는 정적 메서드
public static double GetDistance(double x1, double y1, double x2, double y2)
{
double dx = x2 - x1;
double dy = y2 - y1;
return Math.Sqrt(dx * dx + dy * dy);
}
// 주사위를 굴리는 정적 메서드
public static int RollDice()
{
Random random = new Random();
return random.Next(1, 7); // 1부터 6까지의 정수 중 하나를 반환
}
}
class Program
{
static void Main()
{
Console.WriteLine("=== 게임 계산을 위한 공용 도구함 ===");
// 정적 클래스는 인스턴스화할 수 없습니다.
// GameHelper helper = new GameHelper(); // 컴파일 오류!
// 클래스 이름으로 직접 메서드 호출
double distance = GameHelper.GetDistance(0, 0, 3, 4);
Console.WriteLine($"두 점 (0,0)과 (3,4) 사이의 거리는? {distance}");
int diceValue = GameHelper.RollDice();
Console.WriteLine($"주사위를 굴려 나온 숫자는? {diceValue}");
}
}
[실행 결과]
=== 게임 계산을 위한 공용 도구함 ===
두 점 (0,0)과 (3,4) 사이의 거리는? 5
주사위를 굴려 나온 숫자는? 3
정적 클래스는 유틸리티 메서드 모음이나 상수 정의처럼
상태를 저장하지 않거나, 전역적으로 공유해도 되는 값에 잘 어울립니다.
반대로, 게임 진행 상태나 사용자 상태처럼 자주 변하는 데이터를
정적 필드에 넣어 두면 테스트와 유지보수가 힘들어질 수 있습니다.
| 구분 | 정적 생성자 | 정적 클래스 |
|---|---|---|
| 역할 | 클래스의 정적 멤버를 초기화하는 특별한 메서드 | 정적 멤버들만 모아놓은 특별한 클래스 |
| 선언 | static ClassName() { ... } | public static class ClassName { ... } |
| 호출 | CLR에 의해 자동으로 단 한 번만 호출 | 개발자가 필요할 때마다 직접 클래스이름.멤버로 호출 |
| 제약 | 접근 제한자, 매개변수 사용 불가 | 모든 멤버가 static이어야 함, new로 객체 생성 불가 |