1. 데이터 형식
C#에서 데이터 형식은 다음과 같이 나누어 볼 수 있습니다.
데이터는 크게 숫자나 문자열을 다루는 기본형식과 클래스, 구조체등을 다루는 복합형식으로 나누어 볼 수 있고 데이터의 저장방식에 따라 각각 값 형식과 참조 형식으로 구분할 수 있습니다.
2. 변수
'변수'는 값을 담기 위한 공간을 말하며 다르게는 메모리 확보를 위한 수단으로 해석될 수 있습니다. 예컨데
int i;
라고 하면 컴파일러는 int형 숫자를 담을 수 있을만큼의 메모리 공간을 확보하고
i = 100;
이라고 하면 확보된 메모리공간에 100이라는 값을 저장하게 됩니다. 본래는 메모리 주소를 직접 지정하면서 필요한 값을 저장하거나 확인해야 하지만 그렇게 하기에는 너무 불편하고 위험하기 때문에 '변수명'을 메모리주소로 대신하게 되는 것입니다.
변수는 다음과 같이 선언과 동시에 할당이 가능하며
int i = 100;
데이터 형식만 맞다면 여러 변수를 한꺼번에 선언(변수를 생성함)하는 것도 가능합니다.
int i = 100, j = 200;
3. 값형식(스택)
스택은 메모리할당과 제거가 아주 명확하게 동작하는 방식인데 예를 들어 다음과 같이 변수를 선언했다면
{
int i = 100;
int j = 200;
int x = 10;
int y = 20;
}
메모리에 값은 아래와 같은 형태로 쌓이게 됩니다.
위와 같은 상황에서 코드 블럭이 종료(})되는 시점에 도달하면 각 값은 y부터 i까지의 순서대로 삭제됩니다.
4. 참조형식(힙)
힙은 참조형식에서 사용하는 메모리구조입니다. 하지만 스택메모리도 함께 사용하는데 참조형식의 변수를 사용하면 변수의 값은 힙에, 값이 저장된 힙의 메모리 주소값은 스택에 담게 됩니다.
그래서 만약 다음과 같이 변수가 사용되었다면(object는 참조형식입니다.)
{
object o = 100;
}
힙과 스택에는 아래와 같은 형태로 값이 저장됩니다.
즉, 스택에 있는 힙의 메모리주소값을 가지고 직접 힙으로 찾아가 본래의 값을 저장하고 가져오는 형태입니다. 만약 이 상태에서 블록이 끝나는 지점에 도달하면 스택만 메모리에서 제거되고 힙의 영역은 여전히 살아있게 됩니다.
이때 힙은 존재하지만 힙을 참조하는 스택이 사라지는 경우 CLR의 가비지컬렉터가 힙의 메모리를 자동으로 수거하여 시스템의 메모리를 관리하게 됩니다.
5. 기본 데이터 형식
C#은 숫자, 문자열, 오브젝트등 대략 15가지의 데이터형식을 제공하고 있습니다. 이 중에서 문자열과 오브젝트만 참조형식이며 나머지는 모두 숫자를 다루는 것으로 값형식에 해당합니다.
1) 정수
C#에서는 다음과 같이 총 9가지의 숫자를 다룰 수 있는 형식을 제공하고 있습니다. 각 형식마다 다룰 수 있는 숫자의 범위가 다르므로 적당한 형식을 사용해야 합니다.
형식 | 의미 | 크기 (바이트/비트) | 범위 |
byte | unsigned byte | 1 / 8 | 0 ~ 255 |
sbyte | signed byte | 1 / 8 | -128 ~ 127 |
short | - | 2 / 16 | -32,768 ~ 32,767 |
ushort | unsigned short | 2 / 16 | 0 ~ 65,535 |
int | signed int | 4 / 32 | -2,147,483,648 ~ 2,147,483,647 |
uint | unsigned int | 4 / 32 | 0 ~ 4,294,967,295 |
long | signed long | 8 / 64 | -922,337,203,685,477,508 ~ 922,337,203,685,477,507 |
ulong | unsigned long | 8 / 64 | 0 ~ 18,446,744,073,709,551,615 |
char | 문자 | 2 / 16 |
표에서 singed는 부호있음을 unsigned는 부호 없음을 의미합니다. 이때 숫자를 나타내는 전체 비트중 맨앞에 비트를 부호비트라고 하며
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0이면 양수 1이면 음수로 부호를 나타내는데 사용됩니다. 그래서 sbyte의 경우 127을 표현하려면 다음과 같이 할 수 있으나
64 | 32 | 16 | 8 | 4 | 2 | 1 | |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
부호비트 까지 모두 사용하는 byte라면
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
위와 같이 255까지의 수를 표현할 수 있게 됩니다. 하지만 byte가 아닌 sbyte라면 위의 값은 -127이 아닌 -1이 됩니다. 단순히 0을 표시하려면
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
으로 하면 되지만 맨앞에 비트를 부호비트로만 인식한다면
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
이 값은 -0이라고 해야하는 이상한 현상이 발생하게 되는 것입니다. 실제 이러한 방식으로 수를 표현하는걸 '부호화 절댓값'이라고 하는데 어쨌건 이러한 문제를 해결하기 위해서 '2의 보수법'이 사용됩니다.
예를 들어 -1을 음수로 표시하려면 우선 1의 값을 저장한 후
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
전체 비트를 반전시킵니다. 즉, 0은 1로 1은 0으로 표현하는 것입니다.
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
그리고 이 상태에서 1을 더합니다. 그럼 결과가 아래와 같이 바뀌게 되는데
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
이 값이 -1이 됩니다. 2의 보수법에서는 0이 표현하려는 숫자가 되며 해당 숫자에 +1을 한 결과가 실제 음수값에 해당합니다. 따라서 위의 예제는 0이 없으므로 0 + 1을 하게 되면 -1이 되는 것이죠.
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 |
위의 경우에는 0이 2자리에 있으므로 2가 되며 2 + 1은 3이므로 결과적으로 -3의 값이 됩니다.
위의 정수 데이터형 표를 보면 각 데이터형마다 담을 수 있는 값의 범위가 정해져 있음을 알 수 있습니다. 예를 들어 byte는 0 에서 255 까지의 값만 담을 수 있는 것입니다. 그런데 만약 특정 데이터 형에서 담을 수 있는 범위를 넘어서는 값이 들어오게 되면 당연히 해당 변수의 값은 더 늘어날 수 없는 문제가 생기게 되는데 이를 '오버플로우(Overflow)'라고 합니다. 실제 오버플로우가 발생하면 해당 데이터형의 변수값은 데이터형이 가질 수 있는 최소값으로 바뀌게 됩니다.
따라서 char에서 255가 다음과 같은 상태라면
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
256은
256 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
값이 됩니다. 하지만 char는 8비트이므로 맨앞의 1은 버려지게되고 결국 남은 수가 0이 되어 0값을 가지게 되는 것입니다.
부호를 취급하는 데이터형 또한 동일한 현상이 발생하는데 sbyte라는 맨 앞의 숫자를 부호비트로 사용하는 상태에서 오버플로우가 발생하면 위에서 설명한 2의 보수법때문에 같은 원리로 변수값이 -128이 됩니다. 이러한 이유로 데이터형을 사용할때는 값이 범위를 잘 고려해야 합니다.
참고로 범위를 넘어서는게 아니라 범위아래로 내려가는 경우도 있는데 이를 '언더플로우(Underflow)'라고 하며 오버플로우와 같은 원리로 처리되지만 값은 제일 큰값으로 바뀌게 됩니다.
참고로 변수에 값을 넣을때 필요하다면 자리수를 언더바(_)문자를 사용해 구분해 줄 수 있으며
int i = 1_000_000;
다른 진수 변환은 다음과 같은 방법으로 처리합니다.
static void Main(string[] args)
{
int i = 100;
WriteLine($"2 진수 : {Convert.ToString(i, 2)}");
WriteLine($"8 진수 : {Convert.ToString(i, 8)}");
WriteLine($"10 진수 : {Convert.ToString(i, 10)}");
WriteLine($"16 진수 : {Convert.ToString(i, 16)}");
}
숫자에서 일반 10진수가 아닌 2진수, 8진수, 16진수등의 값을 표현할때는 아래와 같이 접두사를 사용해야 합니다.
static void Main(string[] args)
{
int i = 100;
WriteLine($"{i}");
i = 0b0110_0100; //2진수
WriteLine($"{i}");
i = 0100; //8진수
WriteLine($"{i}");
i = 0x64; //16진수
WriteLine($"{i}");
}
2) 실수
정수가 아닌 2.14처럼 실수를 다루는 데이터형이며 다음과 같은 3가지 형식을 사용할 수 있습니다.
데이터형 | 의미 | 크기 (바이트 / 비트) | 범위 |
float | 단일 정밀도 | 4 / 32 | -3.402823e38 ~ 3.402823e38 |
double | 복수 정밀도 | 8 / 64 | -1.79769313486232e308 ~ 1.79769313486232e308 |
decimal | 실수 | 16 / 128 | ±1.0×10e-28 ~ ±7.9×10e28 |
float은 전체 32비트중 처음 하나는 부호표시로 사용하며 그 다음 8비트는 소수점의 위치를 표시하기 위한 용도로, 나머지 23비트는 수를 표시하는데 사용합니다. double은 flaot에 비해 2개의 공간을 사용하므로 표현의 범위가 훨씬 넓고 decimal또한 double보다 정밀도가 더 높아지게 됩니다. 여기까지 보면 무조건 정밀도가 높은 데이터형을 사용하면 좋다고 생각할 수 있지만 그 만큼 성능면에서는 분리하니 용도에 맞춰 적절한 데이터형을 사용해야 합니다.
실수에서 float는 숫자끝에 f를 붙여야 하며 decimal의 경우에는 끝에 m을 붙여줍니다.
static void Main(string[] args)
{
float f = 1.234f;
double d = 1.234567;
decimal m = 1.23456789m;
}
3) 문자및 문자열
char는 정수형에 속하지만 문자 하나를 다루는데 사용되며
char c = 'a';
문자열은 string형이 사용됩니다.
string s = "hello world!";
보시는 바와 같이 문자는 홀따옴표를 사용하지만 문자열은 쌍따옴표를 사용해 묶어 줘야 합니다.
● 문자열 검색
C#에서는 문자열을 검색하기 위한 다음과 같은 메서드를 제공하고 있습니다.
IndexOf() | 대상 문자열에서 지정한 문자 또는 문자열의 위치를 반환합니다. |
LastIndexOf() | 대상 문자열에서 지정한 문자 또는 문자열을 뒤에서 부터 검색한 위치를 반환합니다. |
StartsWith() | 대상 문자열이 지정한 문자열로 시작하는지의 여부를 반환합니다. |
EndsWith() | 대상 문자열이 지정한 문자열로 끝나는지의 여부를 반환합니다. |
Contains() | 대상 문자열이 지정한 문자열을 포함하는지의 여부를 반환합니다. |
Replace() | 대상 문자열에서 지정한 문자열을 검색해 변경할 문자열로 모두 교체합니다. |
static void Main(string[] args)
{
string s = "hello world!";
WriteLine(s.IndexOf("world"));
WriteLine(s.StartsWith("hello"));
WriteLine(s.Contains("!"));
WriteLine(s.Replace("!", "."));
}
● 문자열 변경
기존 문자열값을 변경하기 위한 메서드는 다음과 같은 것들이 있습니다.
ToLower() | 대문자를 소문자로 변경합니다. |
ToUpper() | 소문자를 대문자로 변경합니다. |
Insert() | 지정한 위치에 새로운 문자열을 삽입합니다. |
Remove() | 지정한 위치부터 지정한 길이만큼의 문자열을 삭제합니다. |
Trim() | 문자열의 앞뒤 공백을 삭제합니다. |
TrimStart() | 문자열의 앞 공백을 삭제합니다. |
TrimEnd() | 문자열의 뒤 공백을 삭제합니다. |
● 문자열 나누기
문자열을 나누기 위해서는 다음의 메서드를 사용할 수 있습니다.
Split() | 대상 문자열에서 지정한 문자 또는 문자열기준으로 분리한 결과를 배열로 반환합니다. |
SubString() | 지정한 위치에서 부터 지정한 길이만큼의 문자열값을 반환합니다. |
● 문자열 서식화
문자열을 서식을 위한 메서드는 다음과 같습니다.
format() | 문자열을 지정한 서식에 맞춰 표시합니다. |
format() 메서드를 사용해 특정 변수값을 출력합니다.
static void Main(string[] args)
{
string s = "Hello World!";
WriteLine(string.Format("변수 s의 값은 {0}입니다.", s));
}
참고로 예제에서 사용한 WriteLine()는 내부적으로 format()메서드를 사용하기에 사용방법이 동일합니다.
문자열은 다음과 같이 임의로 원하는 공간을 만들어 채울 수 있고
static void Main(string[] args)
{
string s = "Hello World!";
WriteLine(string.Format("변수 s의 값은 {0, -30}입니다.", s));
}
-30처럼 -로 값을 지정하여 왼쪽부터 공간을 만들거나 -를 없앤 양수를 지정하여 오른쪽부터 공간을 만들 수 있습니다.
static void Main(string[] args)
{
string s = "Hello World!";
WriteLine(string.Format("변수 s의 값은 {0, -30}입니다.", s));
WriteLine(string.Format("변수 s의 값은 {0, 30}입니다.", s));
}
문자열이 아닌 숫자의 경우에도 특정 서식을 지정해 표현할 수 있는데
static void Main(string[] args)
{
WriteLine("{0:D}", 0xFF); //10진수 표현
WriteLine("{0:X}", 255); //16진수 표현
WriteLine("{0:N}", 123456); //세자리 수 콤마(,) 표현
WriteLine("{0:F}", 12.345); //고정소수점 표현
WriteLine("{0:E}", 12.345678); //지수 표현
}
이때 '자리수 지정자'를 통해 특정 숫자의 자리수를 임의로 확보할 수 있습니다. 이 경우 빈자리는 0으로 채워지게 됩니다.
static void Main(string[] args)
{
WriteLine("{0:D}", 0xFF); //10진수 표현
WriteLine("{0:X}", 255); //16진수 표현
WriteLine("{0:N}", 123456); //세자리 수 콤마(,) 표현
WriteLine("{0:F}", 12.345); //고정소수점 표현
WriteLine("{0:E}", 12.345678); //지수 표현
WriteLine("{0:D5}", 123); //5자릿수 확보
}
날짜및 시간도 특정 서식을 지정할 수 있습니다.
static void Main(string[] args)
{
DateTime dt = DateTime.Now;
WriteLine(dt);
WriteLine("현재 {0:yyyy MM dd tt hh:mm:ss dddd}", dt);
}
y는 년도를 의미하며 yy는 두자리, yyyy는 네자리의 년도를 표시합니다. 마찬가지로 M은 월, d는 일입니다. MM처럼 2개를 사용하면 2자리, 1개를 사용하면 한자리수로 표현합니다. 예컨데 1월에서 한자리를 1로 두자리는 01처럼 표시하는 것입니다.
tt는 오전/오후를 나타냅니다.
h는 시간인데 H처럼 대문자를 사용하는 경우 24시간 단위로 시간을 표시하게 됩니다. m는 분, s는 초를 나타냅니다.
dddd는 요일표시입니다. ddd처럼 3개를 사용하는 경우는 '일', '월' 처럼 약식으로 요일을 나타냅니다.
위 예제에서는 '오전'이나 '목요일'처럼 한글로 표시되어 있는데 이는 현재 컴퓨터에서 설정한 국가별 양식을 그대로 따르는 것입니다.
만약 특정 국가의 양식에 맞게 표시하려면 컴퓨터의 설정을 바꾸는 대신 CultureInfo클래스를 사용해 특정 국가의 포멧을 맞춰주면 됩니다.
using System;
using System.Globalization;
using static System.Console;
namespace test
{
class Program
{
static void Main(string[] args)
{
DateTime dt = DateTime.Now;
WriteLine(dt);
WriteLine("현재 {0:yyyy MM dd tt hh:mm:ss dddd}", dt);
CultureInfo ci = new CultureInfo("en-US");
WriteLine("현재 {0}", dt.ToString("yyyy MM dd tt hh:mm:ss dddd", ci));
}
}
}
● 문자열 보간
위에서 문자열에 서식을 줄때 다음과 같은 방법을 사용했습니다.
static void Main(string[] args)
{
string s = "Hello World!";
WriteLine(string.Format("변수 s의 값은 {0}입니다.", s));
}
이를 문자열 보간법으로 바꾸면 다음과 같이 됩니다.
static void Main(string[] args)
{
string s = "Hello World!";
WriteLine($"변수 s의 값은 {s}입니다.");
}
문자열 서식을 위해 format() 메서드를 필요로 하지 않으며 문자열 앞에 $문자를 사용하고 {0}처럼 서식의 순서를 지정하는 대신 {s}와 같이 변수명을 그대로 사용했다는 차이만 있습니다. 이것 만으로 코드를 훨씬 간결하게 유지할 수 있음을 알 수 있습니다.
참고로 문자열 보간은 C# 6.0부터 사용가능합니다.
4) 논리형
참/거짓을 구분하는 데이터형으로 bool 이 있습니다.
bool b = true;
참고로 false는 0으로 true는 1로 취급될 수 있는데 사용하는 데이터의 범위는 단 1비트 이지만 CPU에서 데이터를 다루는 기본 묶음이 1바이트 이므로 bool은 1바이트의 메모리 공간을 차지합니다.
5) object
모든 데이터형은 기본적으로 object로 부터 상속됩니다. 상속은 클래스를 다룰때 알게될텐데 어찌되었건 모든 데이터형의 조상은 object가 되는 셈입니다.
이 때문에 object는 정수, 문자열, 실수등 모든 데이터형의 데이터를 다룰 수 있습니다.
static void Main(string[] args)
{
object i = 10;
object f = 123.456f;
object s = "hi!!";
WriteLine(i);
WriteLine(f);
WriteLine(s);
}
6) 박싱과 언박싱
다만 object는 참조형식이므로 정수와 같은 값형식의 데이터가 담기게 되면 데이터를 힙에 할당하는 박싱(Boxing)을 수행합니다. 즉, 값을 힙에 할당하고 그 주소를 변수에 담아 변수에서 힙의 메모리를 참조하도록 하는 것입니다.
반대로 object에 있는 값을 가져오려면 그 형식에 맞게 변환을 해줘야 합니다.
static void Main(string[] args)
{
object i = 10;
int j = (int)i;
WriteLine(j);
}
값을 가져오는 경우에는 '언박싱(Unboxing)'이라고 하며 힙에 있는 데이터를 꺼내 값형식의 변수에 저장하는 과정을 거치게 됩니다.
7) 형식 변환
위의 예제를 보면 object형 i변수에 있는 값을 int형 j변수로 가져오기 위해 (int)를 사용하여 값을 int형으로 변환하고 있음을 알 수 있습니다. 이러한 경우를 '형변환'이라고 하는데 일반적인 경우 별 문제가 없지만 형변환시 특정 상황에서는 예상치 못한 결과가 나올 수 있습니다.
그 예로 범위가 다른 형식 사이 혹은 부호 있는 형식과 부호 없는 형식사이의 변환이 있습니다.
static void Main(string[] args)
{
int i = 128;
sbyte c = (sbyte)i;
WriteLine(c);
}
범위가 작은 형식에서 범위가 큰 형식으로 변환하는 경우에는 별 문제가 없으나 그 반대인 경우에는 값이 수용할 수 있는 범위를 넘어서게 되면 오버플로우가, 값이 범위보다 더 아래에 있는 경우 언더플로우가 발생하게 됩니다.
실수 형식 사이의 변환에서는 특히 소수점 이하 정밀도에서 주의를 기울여야 합니다. 범위가 작은 실수형에서 범위 큰 실수형으로의 변환은 큰 문제가 없으나
static void Main(string[] args)
{
float f = 12.345f;
WriteLine(f);
double d = (double)f;
WriteLine(d);
}
범위가 큰 실수형에서 작은형으로의 변환은 당연히 정밀도에서 손상을 입게 되며
static void Main(string[] args)
{
double d = 1.23456789;
WriteLine(d);
float f = (float)d;
WriteLine(f);
}
실수에서 정수로의 변환에서는 아예 소수점 이하는 버려지게 됩니다.
static void Main(string[] args)
{
double d = 12.345;
WriteLine(d);
int i = (int)d;
WriteLine(i);
}
마지막으로 숫자를 문자열로 변한하는 경우나 문자열을 숫자로 변환하는 경우가 있는데 이런 경우는 변환에 필요한 특정 메서드를 사용해야 합니다. 예컨데 아래와 같은 방법으로는 변환이 불가능합니다.
static void Main(string[] args)
{
string s = "1234";
int i = (int)s;
WriteLine(i);
}
만약 문자열에서 특정 숫자형으로 변환하고자 한다면 해당 숫자형식의 Parse() 메서드를 사용해야 합니다.
static void Main(string[] args)
{
string s = "1234";
int i = int.Parse(s);
WriteLine(i);
}
Parse() 메서드 이외에 TryParse() 메서드도 존재하는데 Parse() 메서드는 예를 들어 주어진 값을 변환할 수 없으면 그대로 예외를 일으키고 프로그램실행이 중단되지만 TryParse() 는 미리 형식변환이 가능한지 여부를 확인해 볼 수 있는 여지를 제공합니다.
static void Main(string[] args)
{
string s = "1234a";
if (int.TryParse(s, out int i))
{
WriteLine(i);
}
else
{
WriteLine("변환할 수 없음");
}
}
TryParse는 변환이 불가능하면 false를 반환하게 되고 if문에 의해 평가됩니다. 따라서 위 예제는 "변환할 수 없음"이라는 메세지를 보여줄 것입니다.
6. 상수
상수는 어떤 변수에 한번 값을 선언하면 그 값은 컴파일러에 의해 바꿀 수 없는 변수를 말합니다.
static void Main(string[] args)
{
const string s = "hello";
const int i = 100;
WriteLine(s);
WriteLine(i);
i = 200;
WriteLine(i);
}
const 키워드는 해당 변수가 상수임을 나타냅니다. 상수로 선언된 변수는 i = 200; 처럼 값을 바꿀 수 없으며 값을 바꾸려는 시도가 있는 경우 컴파일되지 않고 오류가 발생합니다.
7. 열거형
열거형은 같은 범주에 속하는 상수들을 모아놓은 형태를 말합니다. 예를 들어 사람이나 동물의 행동을 정의하기 위해 다음과 같이 상수를 사용하는 경우
const int RUN = 1;
const int WALKING = 2;
const int STOP = 3;
이러한 상수를 열거형으로는 다음과 같이 정의할 수 있습니다.
enum [열거형명] { 상수1 = 값1, 상수2, 상수3 ... } |
enum STATE
{
RUN,
WALKING,
STOP
}
static void Main(string[] args)
{
WriteLine(STATE.RUN);
WriteLine(STATE.WALKING);
WriteLine(STATE.STOP);
}
단순한 상수가 아닌 열거형을 사용하는 이유는 상수라도 사람이 코딩을 하는 일이기에 초기값이 같아지는 실수를 할 수 있고
const int RUN = 1;
const int WALKING = 2;
const int STOP = 2;
각각의 상수마다 주석이 아니면 서로가 같은 범주에 속함을 소스코드에서 표현하기가 힘들기 때문입니다, 참고로 열거형 또한 상수처럼 값을 바꿀 수 없습니다.
열거형의 값은 기본적으로는 처음부터 순서대로 0, 1, 2... 과 같은 식으로 값을 배정합니다.
enum STATE
{
RUN,
WALKING,
STOP
}
static void Main(string[] args)
{
WriteLine(STATE.RUN);
WriteLine(STATE.WALKING);
WriteLine(STATE.STOP);
if (0 == (int)STATE.RUN) {
WriteLine("RUN은 순서상 0의 값임");
}
if (1 == (int)STATE.WALKING) {
WriteLine("WALKING은 순서상 1의 값임");
}
if (2 == (int)STATE.STOP) {
WriteLine("STOP은 순서상 2의 값임");
}
}
보시는 바와 같이 열거형은 값이 중복되지 않으며 다음과 같이 값을 임의로 부여하는 경우에도
enum STATE
{
RUN,
WALKING = 5,
STOP
}
static void Main(string[] args)
{
STATE state = STATE.STOP;
if (state == STATE.STOP) {
WriteLine((int)state);
}
}
컴파일러가 알아서 그 다음 요소(STOP)에 값을 +1한 6의 값을 배정합니다.
하지만 다음과 같이 고의적으로 같은 값을 배정하는 경우는 값이 중복됨을 피할 수 없으니 주의해야 합니다.
enum STATE
{
RUN,
WALKING = 5,
STOP = 5
}
enum은 기본적으로 int형으로 다뤄질 수 있지만 아래와 같이 임의의 데이텨형을 지정할 수 있는데
enum STATE : byte
{
RUN,
WALKING = 5,
STOP
}
이때 enum에 사용할 수 있는 데이터형은 byte, sbyte, short, ushort, int, uint, long, ulong 이며 문자열(string)과 같은 형은 사용할 수 없습니다.
8. Nullable
값형식의 변수를 선언하면 해당 변수를 읽기전 반드시 변수의 값이 지정되어 있어야 합니다. 따라서 다음 코드는 오류를 발생시키게 됩니다.
static void Main(string[] args)
{
int a;
WriteLine(a);
}
값형식의 경우 변수를 비워둘 수 없기 때문인데 이를 가능하게 하는 것이 바로 Nullable형식입니다. Nullable을 사용하려면 값형식 데이터형에 물음표(?)만 넣어주면 됩니다.
static void Main(string[] args)
{
int? a = null;
WriteLine(a);
}
물론 Nullable은 값형식에서만 사용가능합니다. 참조형식은 이미 Nullable이 가능하기 때문입니다.
변수를 Nullable로 선언하면 HasValue와 Value라는 2가지 속성을 가지게 되는데 이름에서도 알 수 있듯이 HasValue는 값이 존재하는지의 여부(Null인지 아닌지)를 나타내며 Value는 실제 변수에 들어있는 값을 가져오는 속성입니다.
static void Main(string[] args)
{
int? a = null;
if (a.HasValue) {
WriteLine(a.Value);
}
else {
WriteLine("NULL");
}
}
만약 HasValue속성이 false인데도 Value로 변수의 값을 가져오려 한다면 프로그램은 예외(InvaildOperationException)를 발생시키게 되니 주의해야 합니다.
9. var
C#언어는 본래 사용하려는 값에 따라 특정 데이터형을 반드시 지정해야 하는 언어입니다. 그런데 나중에 알게될 LINQ나 익명 메서드등의 등장으로 인해 var라는 '형식 타입 추론'형을 필요로 하게 됩니다. 즉, 변수에 대입되는 데이터에 따라 형식이 자동으로 결정되는 것입니다. 일일이 int나 string처럼 데이터형을 지정할 필요가 없습니다.
static void Main(string[] args)
{
var i = 100;
var s = "hello world";
var f = 12.345;
WriteLine(i);
WriteLine(s);
WriteLine(f);
}
var는 기존에 존재하는 데이터형뿐만 아니라 임의로 만들어진 사용자 정의 타입이라도 대체되어 사용될 수 있습니다. 하지만 반드시 지역변수로만 사용되어야 하고 변수의 선언과 동시에 값이 필수로 지정되어야 합니다. 뒤에 오는 값에 따라 컴파일러가 형식을 추론해 var i = 100;과 같은 문장을 int i = 100; 처럼 고쳐서 컴파일을 진행하기 때문입니다.
10. CTS (Common Type System)
.NET에서는 .NET을 사용하는 여러 언어들간 호환성보장을 위해 CTS라는 규격을 만들어 놓았습니다. 각각의 언어마다 가지고 있는 데이터형식을 저마다 사용하게 되면 언어들간 호환성을 보장할 수 없기 때문입니다. 예를 들어 VB.NET으로 만들어진 dll 라이브러리를 C#언어를 사용하는 프로그램에서 참조하여 사용할 수 있는데 이런것이 가능한 이유는 C#이나 VB.NET이나 둘다 CTS의 규격을 그대로 따르고 있기 때문입니다.
아래 표는 CTS에서 정의된 데이터형이며 C#과 VB.NET에서 사용하는 데이터형을 표로 나열한 것입니다.
CTS 클래스 (Sytem 네임스페이스) | C# 데이터형 | VB.NET 데이터형 |
Byte | byte | Byte |
SByte | sbyte | SByte |
Int16 | short | Short |
Int32 | int | Integer |
Int64 | long | Long |
UInt16 | ushort | UShort |
UInt32 | uint | UInteger |
UInt64 | ulong | ULong |
Single | float | Single |
Double | double | Double |
Boolean | bool | Boolean |
Char | char | Char |
Decimal | decimal | Decimal |
Object | object | Object |
String | string | String |
표에 따라 C#에서 CTS에 정의된 Sytem.Int32 형식의 데이터형을 사용하려면 int i = 10; 처럼만 해주면 됩니다. C#에서 사용하는 int와 CTS의 System.Int32는 다르지 않은데 본래는 System.Int32 i = 10; 으로 해줘야 하지만 C#에서는 int와 같은 좀더 익숙하거나 혹은 간결한 표현을 제공하는 것입니다. int가 Int32의 별칭역활을 하는 것입니다.
static void Main(string[] args)
{
System.Int32 cts_i = 10;
int cs_i = 20;
WriteLine(cts_i);
WriteLine(cs_i);
}
참고로 특정 변수의 타입을 정확히 확인하려면 GetType() 메서드를 사용하면 됩니다. GetType()은 object에 있는 메서드로 CTS의 데이터형 클래스또한 object로 부터 상속받고 있으므로 해당 메서드의 사용이 가능한 것입니다.
static void Main(string[] args)
{
System.Int32 cts_i = 10;
int cs_i = 20;
WriteLine(cts_i);
WriteLine(cs_i);
WriteLine(cs_i.GetType().ToString());
}
'.NET > C#' 카테고리의 다른 글
[C#] 메서드 (0) | 2021.09.27 |
---|---|
[C#] 제어문 (0) | 2021.09.24 |
[C#] 연산자 (0) | 2021.09.23 |
[C#] 시작하기 (0) | 2021.01.13 |
[C#] MySQL(MariaDB) EntityFramework 사용하기 (0) | 2020.06.04 |