[ C# 타입 ]
- Value Types
- Reference Types
- Gerneric Type Parameters
- Pointer Types
Value Type : Struct , Enum
Reference : All Class, Array, Delegate, Interface, String
Value Type
public struct Point
{
int X;
int Y;
}
static void Main()
{
Point p1 = new Point();
p1.X = 7;
Point p2 = p1; // Assignment causes copy
Console.WriteLine (p1.X); // 7
Console.WriteLine (p2.X); // 7
p1.X = 9; // Change p1.X
Console.WriteLine (p1.X); // 9
Console.WriteLine (p2.X); // 7
}
할당 연산을 한 후 멤버 변수를 바꿔도 할당한 인스턴스에 영향을 안주는 것을 알 수 있습니다.
Reference Type
static void Main()
{
Point p1 = new Point();
p1.X = 7;
Point p2 = p1; // Copies p1 reference
Console.WriteLine (p1.X); // 7
Console.WriteLine (p2.X); // 7
p1.X = 9; // Change p1.X
Console.WriteLine (p1.X); // 9
Console.WriteLine (p2.X); // 9
}
p1과 p2가 같은 인스턴스를 가리키고 있다는 것을 알 수 있습니다. 즉 p1을 수정했을 때 p2까지 영향을 미친다.
[ NULL Reference ]
Reference Type은 null로 할당 할 수 있습니다. 하지만 Value Type은 할당이 되지 않는다.
*Value Type에도 null을 가능하게 하는 Nullable 키워드가 있지만 자주 선언하면 어디에서 Null이 됐는지 알 수 없어지기 때문에 좋지 않다.
[ 저장 추가부담 ]
Value type 변수는 Field만큼의 메모리를 차지합니다.
예를 들어 Struct를 살펴보면 Struct의 멤버 변수만큼의 메모리를 차지하게 됩니다.
struct A
{
byte b; // 1 바이트
long l; // 8 바이트
} // 총 9 바이트?
💡 엄밀히 말하면 CLR은 형식 안의 필드들을 해당 필드 크기의 배수에 해당하는 주소에 배치합니다. 따라서 위 구조체는 실제로는 16바이트를 소비합니다. (첫 필드 다음의 일곱 바이트는 낭비가 됩니다.) StructLayout 특성을 이용하면 이러한 행동을 다른 방식으로 바꿀 수 있다고 합니다.
하지만 Reference type의 변수는 Reference를 담을 변수와 Object를 위한 메모리가 따로 할당됩니다.
Object도 Field의 수만큼 할당되고 추가적으로 관리를 위한 오버헤드 메모리가 할당된다고 합니다.
[ 덤프(Dump) ]
프로그램의 현 메모리 상태에 대한 정보가 적힌 파일.
*보통 현업에서는 크래시가 난 프로그램을 조사하는데에 쓰임.
- 라이브서비스중인 게임들은 콜스택만 보내거나 미니덤프파일을 회사 쪽으로 보내도록 프로그램되어있는 경우가 있음. (그래야 그걸 분석해서 크래시를 수정하니까)
콜스택 : 컴퓨터 프로그램에서 현재 실행 중인 서브루틴에 관한 정보를 저장하는 스택 자료구조
미니덤프 : 예외로 인해서 종료된 프로그램의 정보를 가지고 있는 파일을 의미
*미니덤프 파일은 프로그램이 종료될 당시 메모리 정보와 스택정보들이 남아있어 종료된 원인을 분석하기 좋다.
[ Class와 Struct를 언제 사용해야 할까? ]
Class는 Reference Type 이며 Struct는 Value Type 이기 때문에 각각 Heap, Stack으로 메모리를 할당하게 된다.
기본적으로 Stack에 메모리를 할당하는 것이 Heap에 메모리를 할당하는 것보다 매우 빠르다.
그러면 무조건 Stack에 할당하는 Struct를 쓰는게 좋은 것인가? 라고하면 생각할 거리가 많은 예시이다.
Class로 바꾸더라도 Reference Type의 데이터를 가리키기위한 포인터 데이터는 32bit OS 에서는 4bytes,
64bit OS에서는 8bytes 이다. 그러면 Struct 1개, Class 1개 메모리 사용량은 크게 달라지지 않지만
Class 즉 Reference Type을 사용하면 Heap에 할당 했으므로 추후 GC가 돌아야한다.
그 부분에도 오버헤드가 걸리게 된다.
구조체에 데이터가 많다면 클래스를 데이터가 적다면 구조체를 쓰는게 이득일거 같다는 생각이다.!!
32비트 운영체제 vs 64비트 운영체제
- 주소를 표현하는데에 쓰이는 비트 갯수
데이터 타입은 무엇인가?
"일정 크기 메모리안의 데이터를 어떻게 해석하는지" 이다.
[ 변환 (Conversion) ]
c#에서는 한 형식의 인스턴스를 그 형식과 호환되는 다른 형식의 인스턴스로 변환할 수 있다.
*기존 인스턴스는 새 인스턴스를 생성하는데에 필요한 자료를 제공할 뿐 사라지지 않고 남아있다.
변환에는 암묵적 변환(Implicit Conversion)과 명시적 변환(Explicit Conversion)으로 나누어진다.
암묵적 변환은 자동으로 변환이 가능하며, 명시적 변환은 프로그래머가 캐스팅을 해야한다.
int x = 12345; // int is a 32-bit integer
long y = x; // Implicit conversion to 64-bit integer
short z = (short)x; // Explicit conversion to 16-bit integer
*그렇다면 변환의 조건은 어떻게 될까?
암묵적 변환은 아래 두 조건이 모두 만족되면 사용할 수 있다.
- 컴파일러는 변환의 성공을 보장할 수 있다.
- 변환에 의해 어떠한 정보도 손실되지 않는다.
명시적 변환은 아래 두 조건 중 1개라도 만족하면 반드시 해야한다.
- 변환이 항상 성공할 것임을 컴파일러가 보장할 수 없다.
- 변환 도중 정보가 손실될 수 있다.
[ 캐스팅과 참조 변환 ]
상향 캐스팅
Derived Type이 Base Type의 객체에 참조되는 것입니다. 명시적 캐스팅이 필요없습니다.
Enemy enemy = slime;
하향 캐스팅
반대의 경우이며 명시적 캐스팅이 필요하다.
Slime slime = (Slime)enemy;
*런타임에 하향캐스팅이 실패하면 InvalidCastException 예외가 발생합니다.
( as ) 연산자
() 캐스팅 연산자와 다른점은 as 연산자는 하향 캐스팅이 실패한 경우 null을 반환한다는 것!
하향 캐스팅을 하고 실패 했는지 검사할 때 편리하다.!
- () 캐스팅을 사용한다는 것은 프로그래머가 컴파일러에게 "이 값의 형식이 이것임은 확실하다. 만일 내가 틀렸다면 내 코드에 버그가 있는 것이니 예외를 던져 줘!" 라고 말하는 것과 같다!
- 반면 as 연산자로 캐스팅을 한다는 것은 이 값의 형식이 무엇인지는 확신할 수 없으므로 실행시점에서의 결과에 따라 실행 흐름을 분기하겠다는 뜻이다.
as 연산자는 커스텀 변환을 수행하지 못한다 또한 수치 변환도 수행하지 못한다.
long x = 3 as long; // 컴파일시점에서 오류
( is ) 연산자
is 연산은 하향 캐스팅의 성공 여부를 판정해준다.
하향 캐스팅을 사용하기 전에 주로 쓰이게 된다.
if(a is Stock)
Console.WriteLine(((Stock)a).SharesOwned);
is 연산자는 언박싱 변환의 성공 여부도 판정해준다. 그러나 커스텀 변환, 수치 변환은 고려하지 않는다.
[ Boxing & UnBoxing ]
Object 형식
모든 타입의 기반 클래스는 Object(System.Object)이다.
c#의 이런 특성을 Type Unification이라고 한다.
[ Boxing ]
값 형식의 인스턴스 값을 참조 형식의 인스턴스로 변환하는 것을 박싱이라고 합니다.
참조형식은 인터페이스나 object클래스이어야 합니다.
박싱 예)
int x = 9;
object obj = x // int 박싱
[ UnBoxing ]
참조 형식의 인스턴스를 값 형식의 인스턴스로 변환하는 것이 언박싱이 됩니다.
언박싱 예)
int y = (int)obj;
*언박싱에서는 명시적 캐스팅이 필수적입니다.
박싱과 언박싱의 복사의미론
박싱은 값 형식 인스턴스를 새 객체로 복사하고, 언박싱은 객체의 내용을 다시 값 형식 인스턴스로 복사합니다.
다음 예에서 i의 값을 바꿔도 그 전에 박싱된 복사본이 변하지 않음을 주목하기 바랍니다.
int i = 3;
object boxed = i;
i = 5;
Console.WriteLine(boxed); // 3
[ String ]
string은 Immutable이다.
바꿀수 없다는 뜻.
그래서 바꾸기위해서는 이전꺼는 바꿀수 없으므로 새로운 객체를 또 만들어서 새로 생성한다.
string s4 = "";
foreach(var c in s2)
{
s4 += c;
}
이러한 형태의 코드가 있으면 s4는 계속해서 반복하면서 새로운 객체를 생성할 것이다. Immutable이기 때문이다.
그래서 StringBuilder를 사용하는 것을 적극 추천한다.
StringBuilder sb = new StringBuilder();
sb.Append(s1);
sb.Append(s2);
sb.Append(s3);
Console.WriteLine(sb.ToString());
StringBuilder를 사용하면 문자열을 조합할때마다 새로운 변수를 생성하지 않고 결합할 수 있다.
내부에 함수가 존재해서 값들을 조합하거나 삭제할때에도 새로운 인스턴스가 생성되지 않습니다.
'C#' 카테고리의 다른 글
C# - Coroutine (0) | 2024.07.06 |
---|---|
C# - 2 (0) | 2024.05.19 |
C# - Custom Type2 (2) | 2024.04.11 |
Virtual Table (0) | 2024.04.11 |
Class (0) | 2024.04.09 |