C#은 정적 타입 언어입니다. 모든 변수, 필드, 식에는 컴파일 시점에 알려진 타입이 있습니다. 언어에는 고유한 키워드(int, double, string...)를 가진 기본 제공 타입이 있습니다. 각 키워드는 System 네임스페이스에 있는 타입의 별칭이므로 int와 System.Int32는 같은 타입이고, string과 System.String도 같은 타입입니다.
기본 제공 타입
| 타입 | .NET 타입 | 크기 | 범위 또는 정밀도 | 리터럴 |
|---|---|---|---|---|
bool | Boolean | 1바이트 | true 또는 false | true |
byte | Byte | 1바이트 | 0부터 255 | 200 |
sbyte | SByte | 1바이트 | -128부터 127 | -5 |
short | Int16 | 2바이트 | -32,768부터 32,767 | 1200 |
ushort | UInt16 | 2바이트 | 0부터 65,535 | 60000 |
int | Int32 | 4바이트 | -2,147,483,648부터 2,147,483,647 | 42 |
uint | UInt32 | 4바이트 | 0부터 4,294,967,295 | 42u |
long | Int64 | 8바이트 | 약 ±9.2 × 10^18 | 42L |
ulong | UInt64 | 8바이트 | 0부터 약 1.8 × 10^19 | 42ul |
float | Single | 4바이트 | 약 ±3.4 × 10^38, 약 6~9자리 | 2.5f |
double | Double | 8바이트 | 약 ±1.8 × 10^308, 약 15~17자리 | 2.5 또는 2.5d |
decimal | Decimal | 16바이트 | 약 ±7.9 × 10^28, 28~29자리 | 2.5m |
char | Char | 2바이트 | UTF-16 코드 단위 하나, U+0000부터 U+FFFF | 'A' |
string | String | 참조 | 길이 제한 없는 텍스트, 불변 | "hello" |
object | Object | 참조 | 모든 타입의 기반 | new object() |
접미사 없는 정수 리터럴은 int이고, int에 담기지 않을 만큼 크면 uint, long, ulong 중 그 값을 담을 수 있는 첫 번째 타입이 됩니다. 소수점이 있는 리터럴은 double입니다. 그래서 float f = 2.5;와 decimal d = 2.5;는 컴파일되지 않습니다. 리터럴이 double인데 double에서 float나 decimal로의 암시적 변환이 없기 때문입니다. 접미사를 붙이세요: 2.5f, 2.5m.
모든 숫자 타입은 한계값을 상수로 제공합니다:
출력:
byte: 0 to 255
short: -32768 to 32767
int: -2147483648 to 2147483647
long: -9223372036854775808 to 9223372036854775807
decimal max: 79228162514264337593543950335
double max: 1.798E+308
sizes: int 4, long 8, double 8, char 2
255 161 3000000000
실무에서는 대부분 이 중 네 가지를 씁니다. 개수와 인덱스에는 int, ID와 큰 합계에는 long, 측정값과 수학 계산에는 double, 금액에는 decimal입니다.
decimal과 double
double(과 float)은 숫자를 이진수로 저장합니다. 0.1을 포함해 대부분의 십진 소수는 정확한 이진 표현이 없으므로 가장 가까운 표현 가능한 값으로 저장되고, 작은 오차가 쌓입니다. decimal은 10진 정수와 소수점 위치(scale)를 저장하므로 0.1은 정확히 0.1입니다.
출력:
False
True
0.30000000000000004
0.3
double sum == 1.0: False
decimal sum == 1.0: True
Total: 59.97
Split three ways: 19.99
금액, 가격, 세금처럼 사람이 손으로 더해 보고 비교할 값에는 decimal을 쓰세요. 입력값의 끝자리 0을 유지하고(1.50m은 1.50으로 출력됩니다) 유효 숫자가 28~29자리입니다.
물리적 측정값, 통계, 기하, 게임에는 double을 쓰세요. 몇 배 빠르고, 메모리를 절반만 쓰고, 범위가 엄청나게 넓습니다. 계산된 두 double을 ==로 비교하지 말고, 차이를 허용 오차와 비교하세요: Math.Abs(x - y) < 1e-9.
decimal도 만능은 아닙니다. 1m / 3m은 여전히 0.3333333333333333333333333333입니다. 또 double 연산은 0으로 나눠도 예외를 던지지 않지만(무한대, 0.0 / 0.0이면 NaN이 됩니다), decimal과 정수를 0으로 나누면 DivideByZeroException이 발생합니다.
정수 오버플로와 checked
타입의 범위를 넘는 정수 연산은 기본적으로 조용히 값이 돌아갑니다. 컴파일러는 상수 식의 오버플로는 잡지만, 값이 변수에서 올 때는 잡지 않습니다:
출력:
-2147483648
OverflowException
-1294967296
2147483648
checked(...)나 checked { ... } 블록은 오버플로를 OverflowException으로 바꿉니다. <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>로 프로젝트 전체에 켤 수도 있습니다. 상수로 int x = int.MaxValue + 1;이라고 쓰면 컴파일러가 오버플로를 볼 수 있으므로 컴파일 오류(CS0220)입니다.
마지막 줄이 대부분의 오버플로 버그를 고치는 방법을 보여 줍니다. 연산하기 전에 더 넓은 타입으로 변환하세요. (long)(max + 1)은 틀립니다. 캐스트가 실행될 때는 이미 덧셈에서 값이 돌아간 뒤이기 때문입니다.
값 형식과 참조 형식
모든 C# 타입은 값 형식이거나 참조 형식이며, 이 차이가 대입의 동작을 결정합니다.
- 값 형식: 모든 숫자 타입,
bool,char,DateTime, 열거형, 모든struct. 변수가 데이터를 담고, 대입하면 복사됩니다. - 참조 형식:
string, 배열,object, 모든class. 변수가 객체에 대한 참조를 담고, 대입하면 참조가 복사되므로 두 변수가 같은 객체를 봅니다.
출력:
a=10 b=99
first[0]=99
s1=cat s2=cats
string은 참조 형식이지만 실제로는 값처럼 동작합니다. 불변이고, ==는 참조가 아니라 텍스트를 비교합니다. 사용자 정의 값 형식은 구조체에서 더 다룹니다.
char와 bool
char는 작은따옴표로 쓰는 UTF-16 코드 단위 하나입니다. 내부적으로 숫자 타입이라 int로 변환되고 산술 연산을 지원합니다:
출력:
66
C
True
True
7
b
True
bool은 정확히 두 값만 가지며 숫자와 서로 변환되지 않습니다. if (count)는 0이 아닌지 검사하는 것이 아니라 컴파일 오류입니다. if (count != 0)이라고 쓰세요. 대부분의 이모지처럼 기본 다국어 평면(BMP) 밖의 문자는 char 두 개(서로게이트 쌍)를 차지하므로, string의 Length는 눈에 보이는 문자가 아니라 UTF-16 단위를 셉니다.
기본값
필드, 배열 요소, default(T)는 타입의 기본값을 받습니다: 숫자는 0, bool은 false, char는 '\0', 참조 형식은 null입니다. 지역 변수는 기본값을 받지 않으므로 사용 전에 대입해야 합니다.
출력:
0
0
False
True
0001-01-01
0,0,0
C# 7.1부터는 컴파일러가 추론할 수 있으면 타입 없이 default 리터럴을 쓸 수 있습니다(int x = default;). 값 형식은 null이 될 수 없으며, 그럴 때 쓰는 것이 int?(nullable 값 형식)입니다. nullable 형식을 참고하세요.
네이티브 크기 정수와 큰 정수
특수한 경우를 위한 숫자 타입이 두 가지 더 있습니다. nint와 nuint(C# 9)는 포인터 크기의 정수로, 플랫폼에 따라 32비트나 64비트이며 상호 운용과 저수준 코드에 쓰입니다. System.Numerics.BigInteger는 속도를 희생하는 대신 어떤 크기의 정수든 담습니다:
using System.Numerics;
BigInteger factorial = 1;
for (int i = 2; i <= 30; i++) factorial *= i;
// 265252859812191058636308480000000
자주 묻는 질문
C#의 자료형에는 어떤 것이 있나요?
기본 제공 타입은 정수(sbyte, byte, short, ushort, int, uint, long, ulong), 부동소수점 타입 float와 double, 정확한 십진 연산을 위한 decimal, UTF-16 문자 하나를 담는 char, bool, 그리고 참조 형식 string과 object입니다. 각 키워드는 .NET 타입의 별칭입니다. int는 System.Int32, string은 System.String입니다.
C#에서 금액에는 decimal과 double 중 무엇을 써야 하나요?
decimal을 쓰세요. double은 이진 소수를 저장하므로 0.1 같은 값을 정확히 표현할 수 없고 합계가 조금씩 어긋납니다(0.1 + 0.2 == 0.3은 false). decimal은 10진 숫자를 저장하고 유효 숫자 28~29자리를 유지하며, 회계에서 기대하는 결과를 냅니다. double은 더 빠르고 범위가 훨씬 넓으므로 과학 계산, 그래픽스, 측정값에 쓰세요.
C#에서 int의 최대값은 얼마인가요?
int.MaxValue는 2,147,483,647이고 int.MinValue는 -2,147,483,648입니다(32비트 부호 있는 정수). int.MaxValue에 1을 더하면 조용히 int.MinValue로 돌아가며, checked 컨텍스트에서 실행될 때만 OverflowException을 던집니다. 더 큰 값에는 long(약 922경까지)을 쓰세요.
C#에서 float와 double의 차이는 무엇인가요?
float는 32비트로 유효 숫자가 약 6~9자리이고, double은 64비트로 약 15~17자리입니다. 부동소수점 리터럴은 기본적으로 double이므로 float에는 f 접미사가 필요합니다: float speed = 2.5f;. 메모리나 API(Unity의 Vector3 등)가 float를 요구하지 않는다면 double을 쓰세요.
C#에서 값 형식과 참조 형식의 차이는 무엇인가요?
값 형식 변수(int, double, bool, DateTime, 모든 struct)는 데이터 자체를 담고, 대입하면 복사됩니다. 참조 형식 변수(string, 배열, 모든 class)는 힙에 있는 객체에 대한 참조를 담고, 대입하면 참조가 복사되므로 두 변수가 같은 객체를 가리킬 수 있습니다.