static은 객체가 아니라 타입에 속하는 멤버를 표시합니다. 인스턴스 필드는 객체마다 하나씩 존재하고, 정적 필드는 딱 하나만 존재합니다. 인스턴스 메서드는 객체에서 실행되고(order.Ship()), 정적 메서드는 타입에서 실행되며(Math.Round(x)) 객체가 전혀 필요 없습니다.
정적 필드: 하나뿐인 공유 복사본
차이를 가장 분명히 보는 방법은 카운터입니다. 각 Order는 자기 Id를 갖지만, 지금까지 만든 주문 수는 공유되는 어딘가에 있어야 합니다.
출력:
Ana #1, Ben #2, Chen #3
Orders created: 3
정적 멤버는 a.Created가 아니라 타입 이름 Order.Created로 접근합니다. C#은 인스턴스를 통해 정적 멤버에 접근하는 것을 허용하지 않으므로(CS0176 오류), 호출 지점에서 값이 공유된다는 것이 분명합니다.
정적 메서드
정적 메서드에는 this가 없습니다. 매개변수와 다른 정적 멤버로만 일합니다. 그래서 입력으로부터 무언가를 계산하는 헬퍼에 자연스러운 형태입니다:
출력:
61.38 EUR
7
Main 자체도 정적입니다. 런타임이 어떤 객체도 존재하기 전에 호출합니다.
CS0120 오류: 인스턴스 멤버를 쓰는 정적 코드
Main이 정적이므로, 입문자는 두 번째 메서드를 추가하자마자 이 오류를 자주 만납니다:
class Program
{
int visits;
void Greet() { Console.WriteLine("Hello"); }
static void Main()
{
Greet(); // error CS0120: An object reference is required for the non-static field, method, or property 'Program.Greet()'
visits++; // error CS0120: An object reference is required for the non-static field, method, or property 'Program.visits'
}
}
정적 메서드에는 객체가 없으므로 증가시킬 visits가 없습니다. 해결책은 두 가지이며, 멤버가 무엇이냐에 따라 맞는 쪽이 다릅니다:
- 멤버가 정말 객체별 데이터라면: 객체를 만들어 쓰세요.
var app = new Program(); app.Greet(); - 멤버에 객체 상태가 필요 없다면:
static으로 표시하세요.static void Greet() { ... }
오류를 없애려고 모든 것을 static으로 표시하면 아주 작은 프로그램에서는 동작하지만 실제 프로그램에서는 전역 상태가 됩니다. 데이터가 한 객체에 속하는지 프로그램 전체에 속하는지 물어서 결정하세요.
반대 방향은 항상 허용됩니다. 인스턴스 메서드는 정적 멤버를 자유롭게 호출할 수 있습니다.
정적 클래스
static으로 선언한 클래스는 정적 멤버만 담을 수 있고 절대 인스턴스화할 수 없습니다. 함수와 상수를 묶은 이름 붙은 그룹입니다.
출력:
77
100
True
컴파일러가 약속을 강제합니다. 정적 클래스 안의 인스턴스 멤버는 CS0708 오류이고, 여기에 new를 쓰면 CS0712 오류입니다. Math, Console, File, Path, Enumerable은 모두 정적 클래스입니다. 확장 메서드도 정적 클래스 안에 있어야 합니다.
정적 생성자와 static readonly
값을 정하는 데 식 하나 이상이 필요한 정적 필드는 정적 생성자에서 설정할 수 있으며, 런타임이 타입을 처음 쓰기 전에 한 번 실행합니다:
출력:
Before first use
ShippingRates initialized
5.50
3
2026-01-01
readonly는 필드를 이니셜라이저나 정적 생성자에서만 대입할 수 있다는 뜻입니다. 그 뒤로는 다시 대입할 수 없지만, 필드가 가리키는 객체는 여전히 변경 가능할 수 있으며, 그래서 딕셔너리를 IReadOnlyDictionary로 노출합니다.
const와 static readonly
둘 다 프로그램 전체가 공유하는 이름 붙은 값을 주며, 값이 정해지는 시점이 다릅니다:
const | static readonly | |
|---|---|---|
| 값이 정해지는 시점 | 컴파일 시점 | 실행 시점, 한 번 |
| 허용 타입 | 숫자, char, bool, string, 열거형, null | 모든 타입 |
| 암시적 static | 예(static const라고 쓸 수 없음) | 명시적으로 static 선언 |
| 호출하는 어셈블리로 복사 | 예 | 아니요, 필드에서 읽음 |
switch case와 특성 인수에 사용 | 가능 | 불가 |
복사 행은 라이브러리에서 중요합니다. 어셈블리 A가 public const int MaxItems = 50;을 선언하고 어셈블리 B가 이를 쓰면, B에는 리터럴 50이 들어갑니다. A를 100으로 바꿔도 B를 다시 컴파일하기 전까지 B에는 아무 변화가 없습니다. 버전 사이에 바뀔 수 있는 값에는 static readonly를 우선 쓰세요.
using static
using static 지시문(C# 6)은 타입의 정적 멤버를 가져오므로 타입 이름 없이 호출할 수 있습니다:
출력:
Area: 28.27
40
수학 계산이 많은 코드에서는 읽기 좋습니다. 그 밖에서는 아껴 쓰세요. 맨 WriteLine이나 Parse는 메서드가 어느 타입에서 왔는지 숨깁니다.
정적 상태와 스레드
정적 필드는 프로세스의 모든 스레드가 공유합니다. 여러 스레드에서 동시에 하나를 증가시키면 갱신을 잃습니다. created++는 읽기, 더하기, 쓰기이고 두 스레드가 이를 섞어 실행할 수 있기 때문입니다. 카운터에는 Interlocked.Increment(ref created)를, 더 큰 것에는 lock을 쓰세요. 이 문제가 주로 터지는 곳은 웹 애플리케이션입니다. 모든 요청이 어떤 스레드에서 실행되므로, "현재 사용자"를 담은 정적 필드는 모든 사용자가 공유합니다.
미묘한 점이 하나 더 있습니다. 제네릭 클래스에서는 구성된 타입마다 자기 정적 필드를 가집니다. Cache<int>.Count와 Cache<string>.Count는 서로 다른 두 필드입니다.
static을 언제 쓸까
좋은 용도:
- 인수로부터 결과를 계산하는 순수 헬퍼: 서식화, 변환, 검증.
- 프로그램이 공유하는 상수와 읽기 전용 조회 데이터.
TimeSpan.FromMinutes(5)나 직접 만든Temperature.FromCelsius(21)같은 팩터리 메서드.- 확장 메서드.
나쁜 용도:
- 변경 가능한 전역 상태(현재 사용자, 장바구니, 바뀌는 설정). 이를 건드리는 모든 코드를 서로 묶고, 테스트가 서로의 잔여물에 의존하게 만듭니다.
- 시계, 데이터베이스, 이메일 발송기처럼 테스트에서 바꾸고 싶을 모든 것. 정적 호출은 가짜로 대체할 수 없지만, 생성자로 넘긴 인스턴스는 가능합니다.
자주 묻는 질문
C#에서 static은 무슨 뜻인가요?
static 멤버는 어떤 객체가 아니라 타입 자체에 속합니다. 객체가 몇 개 있든 정적 필드는 하나뿐이고, 정적 메서드는 인스턴스를 만들지 않고 타입 이름으로 호출합니다(Math.Max(3, 7)). 정적 메서드에는 this가 없으므로 인스턴스 필드를 직접 쓸 수 없습니다.
C#에서 정적 클래스란 무엇인가요?
static으로 선언한 클래스는 정적 멤버만 담을 수 있고 절대 인스턴스화할 수 없습니다(여기에 new를 쓰면 CS0712 오류). Math, Console, File처럼 객체 상태가 필요 없는 관련 함수들을 묶습니다. 확장 메서드는 정적 클래스 안에 선언해야 합니다.
"An object reference is required for the non-static field, method, or property"는 어떻게 해결하나요?
CS0120 오류로, 정적 메서드(흔히 Main)가 인스턴스 멤버를 쓰고 있다는 뜻입니다. 객체를 만들어 그 객체에서 멤버를 호출하거나(var app = new Program(); app.Run();), 멤버가 객체별 데이터를 필요로 하지 않는다면 static으로 표시하세요.
C#에서 const와 static readonly의 차이는 무엇인가요?
const는 컴파일 시점 상수(숫자, 문자열, bool, 열거형)로, 그 값이 이를 쓰는 모든 어셈블리에 복사됩니다. static readonly 필드는 실행 시점에 이니셜라이저나 정적 생성자에서 한 번 대입되며, DateTime이나 리스트 같은 어떤 타입이든 담을 수 있습니다. 절대 바뀌지 않을 값에는 const를, 그 밖의 모든 것에는 static readonly를 쓰세요.
C#에서 static은 언제 써야 하나요?
매개변수만으로 일하는 코드(가격 서식화 같은 순수 헬퍼), 프로그램 전체에 진짜로 전역인 상수와 캐시, 팩터리 메서드에 static을 쓰세요. 사용자별이나 요청별 상태를 담는 것, 또는 테스트에서 교체하고 싶을 것에는 static을 피하세요. 공유되는 변경 가능 상태는 테스트하기 어렵고, 잠금 없이는 스레드 간에 안전하지 않습니다.