변수는 타입이 정해진, 이름 붙은 저장 공간입니다. C#에서는 타입과 이름으로 변수를 선언하고, 원하면 같은 문장에서 값을 줍니다. 타입은 이후에 절대 바뀌지 않습니다. int 변수는 존재하는 동안 계속 정수를 담습니다.
출력:
Priya bought 5 items for 20.25
10
잘못된 타입의 값을 대입하면 컴파일 오류입니다. quantity = "five";는 CS0029(cannot implicitly convert type 'string' to 'int')로 실패합니다. 타입 간 변환은 명시적으로 합니다. 형 변환을 참고하세요.
var: 컴파일러가 타입을 추론하게 하기
var는 초기화 식으로부터 타입이 추론되는 지역 변수를 선언합니다. 동적 타입이 아닙니다. 타입을 직접 쓴 것과 똑같이 컴파일 시점에 고정됩니다.
출력:
String
Int32
Decimal
Double
List`1
var에는 세 가지 규칙이 있습니다. 지역 변수에만 쓸 수 있습니다(필드나 매개변수에는 안 됩니다). 선언에서 초기화해야 합니다. var x;는 CS0818 오류 "Implicitly-typed variables must be initialized"입니다. 그리고 초기화 식에 타입이 있어야 합니다. null만으로는 타입이 없으므로 var nothing = null;은 컴파일되지 않습니다.
언제 쓸지는 팀 스타일의 문제입니다. 보통의 지침은 오른쪽에서 타입이 분명하면 var를 쓰고, 그렇지 않으면 타입을 쓰는 것입니다:
var orders = new List<Order>(); // obvious: var is fine
var reader = new StreamReader(path); // obvious
decimal discount = CalculateDiscount(); // not obvious: name the type
int count = items.Count(); // makes the intent clear
미묘한 함정이 있습니다. var total = 0;은 total을 int로 만들기 때문에 나중에 2.5를 더하면 실패하고, var price = 10;은 decimal이 아니라 int입니다. 리터럴의 타입이 원하는 타입이 아닐 때는 타입을 쓰거나 접미사(10m, 0.0)를 붙이세요.
범위
지역 변수는 선언된 곳부터 그 변수가 선언된 블록 { }의 끝까지 존재합니다. 루프 헤더나 if 본문에서 선언한 변수는 그 뒤에서 보이지 않습니다.
출력:
high
240
C나 C++와 달리 C#에서는 안쪽 블록이 바깥쪽 블록의 지역 변수와 같은 이름을 선언할 수 없습니다(CS0136 오류). 한 메서드 안에서 이름은 하나의 의미만 가져야 합니다. 형제 관계인 두 블록은 이름을 재사용할 수 있습니다. 두 개의 별도 for 루프가 각각 i를 선언하는 것이 그 예입니다.
확정 할당
지역 변수에는 기본값이 없습니다. 컴파일러는 메서드의 모든 경로를 추적하고, 변수가 대입되기 전에 읽힐 가능성이 있으면 컴파일을 거부합니다:
int discount;
if (isMember)
{
discount = 10;
}
Console.WriteLine(discount);
// error CS0165: Use of unassigned local variable 'discount'
선언할 때 초기화하거나(int discount = 0;) 모든 분기에서 대입하면(else 추가) 해결됩니다. 필드와 배열 요소는 다릅니다. 이들은 타입의 기본값(0, false, null)으로 자동 설정됩니다.
const: 컴파일 시점 상수
const는 컴파일 시점에 고정되는 값입니다. 상수 식(리터럴, 다른 const, 또는 이들의 산술 연산)으로 초기화해야 하며, 숫자, char, bool, string, 열거형, null만 될 수 있습니다.
출력:
Price in EUR
120.00 EUR
350
const 필드는 암시적으로 static이므로 타입 이름으로 접근하며(Pricing.VatRate), static const라고 쓰면 오류입니다. 컴파일러는 값을 사용하는 모든 곳에 복사합니다. 그래서 const는 실행 시점 비용이 없고, 또 그래서 라이브러리의 public const는 절대 바뀌지 않을 값(일주일의 일수 같은)이어야 합니다.
readonly와 static readonly
readonly 필드는 선언에서나 그 클래스의 생성자에서만 대입할 수 있습니다. 생성이 끝나면 바꿀 수 없습니다. const와 달리 값은 실행 시점에 계산되고, 어떤 타입이든 될 수 있으며, 객체마다 다를 수 있습니다.
출력:
ACC-17 opened 2024-05-02
Launch: 2020-01-15
basic, pro
마지막 줄들이 흔한 오해를 보여 줍니다. readonly는 필드를 보호하지, 필드가 가리키는 객체를 보호하지 않습니다. Plans는 항상 같은 배열을 가리키지만 배열의 요소는 여전히 바뀔 수 있습니다. 정말로 불변인 컬렉션이 필요하면 ReadOnlyCollection<T>나 ImmutableArray<T>를 쓰세요.
세 가지 요약:
const | readonly | static readonly | |
|---|---|---|---|
| 값이 정해지는 시점 | 컴파일 시점 | 실행 시점 | 실행 시점 |
| 허용 타입 | 숫자, char, bool, string, 열거형, null | 모든 타입 | 모든 타입 |
| 설정 위치 | 선언에서만 | 선언 또는 생성자 | 선언 또는 정적 생성자 |
| 객체별인가 공유인가 | 공유(암시적 static) | 객체별 | 공유 |
| 지역 변수 | 가능(const int x = 1;) | 불가 | 불가 |
명명 규칙
C# 이름은 대소문자를 구분하며(total과 Total은 다른 변수), 글자나 밑줄로 시작해야 합니다. 거의 모든 C# 코드가 따르는 Microsoft 규칙은 다음과 같습니다:
- 지역 변수와 매개변수: camelCase(
orderCount,unitPrice). - 상수, 속성, 메서드, 클래스, public 필드: PascalCase(
MaxItems,VatRate). - private 필드: 앞에 밑줄을 붙인 _camelCase(
_balance). .NET 코드에서 널리 쓰이는 관례입니다. - 불리언은 질문처럼 읽히게:
isActive,hasDiscount,canRetry. - 약어와 헝가리안 접두사(
strName,iCount)는 피합니다.
C# 키워드는 이름이 될 수 없지만, 앞에 @를 붙이면 됩니다. var @class = "5B";는 올바른 코드이며, 생성된 코드나 JSON 필드 이름에 가끔 유용합니다.
값 교환과 다중 대입
C# 7부터는 튜플로 한 문장에서 여러 변수에 대입할 수 있어서 한 줄로 값을 교환할 수 있습니다:
출력:
20 10
Lisbon: 545,000
두 번째 형태는 분해입니다. 튜플로부터 city를 string으로, population을 int로 선언합니다. 자세한 내용은 튜플에 있습니다.
자주 묻는 질문
C#의 var는 무엇인가요?
var는 컴파일러에게 초기화 값으로부터 지역 변수의 타입을 추론하라고 알립니다. var count = 5;는 int를 선언합니다. 변수는 여전히 정적 타입이라 나중에 문자열을 담을 수 없습니다. var는 같은 문장에서 초기화되는 지역 변수에만 쓸 수 있습니다.
C#에서 const와 readonly의 차이는 무엇인가요?
const는 컴파일 시점 상수입니다. 값을 컴파일할 때 알 수 있어야 하고(숫자, 문자열, bool, null), 암시적으로 static이며, 사용하는 모든 곳에 값이 복사됩니다. readonly 필드는 실행 시점에 선언이나 생성자에서 한 번 설정되고, 어떤 타입이든 담을 수 있으며, 객체마다 값이 다를 수 있습니다.
언제 const 대신 static readonly를 써야 하나요?
DateTime, 배열, 객체처럼 컴파일 시점 상수가 될 수 없는 공유 값이나, 라이브러리 버전에 따라 바뀔 수 있는 public 값에는 static readonly를 쓰세요. const 값은 호출하는 어셈블리에 복사되므로, public const를 바꾸면 그것을 쓰는 모든 어셈블리를 다시 컴파일해야 합니다.
C#의 "use of unassigned local variable"은 무슨 뜻인가요?
CS0165 오류입니다. 컴파일러가 지역 변수에 값이 대입되기 전에 읽히는 경로를 찾았다는 뜻입니다. C#에서 지역 변수에는 기본값이 없습니다. 선언할 때 초기화하거나, if/switch의 모든 분기에서 사용 전에 대입되도록 하세요.
C#에서 var와 명시적 타입 중 무엇을 써야 하나요?
둘 다 괜찮고 팀마다 스타일을 정합니다. 흔한 규칙은 오른쪽에서 타입이 분명하면 var를 쓰고(var list = new List<string>();), 분명하지 않으면 타입을 쓰는 것입니다(decimal total = GetTotal();). .NET 런타임 코드베이스도 대략 이 규칙을 따릅니다.