반응형
✅ 해시테이블 (Hash Table)
📌 개념
- 키(key)를 해시 함수(Hash Function)에 넣어 나온 해시값을 기반으로 데이터를 저장하는 자료구조.
- 내부적으로는 배열(array)와 연결 리스트 또는 트리 구조를 함께 사용해 충돌을 해결함.
📌 동작 방식
- Key → 해시 함수 → 인덱스 계산
- 계산된 인덱스에 값을 저장
- 같은 인덱스에 이미 값이 있으면 충돌(Collision) 발생
- 충돌 해결법: 체이닝(Chaining), 오픈 어드레싱(Open Addressing)
📌 시간 복잡도 (평균적 상황 기준)
- 삽입: O(1)
- 삭제: O(1)
- 탐색: O(1)
※ 최악의 경우에는 O(n) (모든 키가 같은 인덱스로 해시됨)
✅ 딕셔너리 (Dictionary in C#)
📌 개념
- C#에서의 Dictionary<TKey, TValue>는 해시테이블 기반으로 구현된 제네릭 키-값(key-value) 쌍 자료구조.
- 즉, 해시테이블을 제네릭 버전으로 구현한 클래스라고 보면 됨.
📌 사용 예시
Dictionary<string, int> ages = new Dictionary<string, int>();
ages["Alice"] = 25;
ages["Bob"] = 30;
Console.WriteLine(ages["Alice"]); // 25
📌 주요 메서드
- Add(key, value) : 새 항목 추가
- Remove(key) : 항목 제거
- ContainsKey(key) : 특정 키 존재 여부 확인
- TryGetValue(key, out value) : 키가 있으면 값을 out 파라미터로 반환
🔍 해시테이블 vs 딕셔너리 차이점
제네릭 지원 | ❌ (object 기반) | ✅ |
타입 안정성 | 낮음 (박싱/언박싱 필요) | 높음 |
성능 | 상대적으로 느릴 수 있음 | 더 빠름 |
쓰레드 안전 | 기본적으로 안전하지 않음 | 마찬가지 (별도 처리 필요) |
사용 권장 | 구형 코드 유지 보수시 | 신규 개발시 권장 |
사실 해시테이블의 상위호환이 딕셔너리 라고 한다. 해시테이블은 사용할떄마다 박싱/언박싱을 하기 때문에 너무 비효율 적이다. 쓰레드까지 들어간다면 솔직히 잘 모르겠다.. 어떻게 안전하지 않다는 기준은 뭔진 모르겠다.
그리고 제네릭 지원이라는 것이 궁금했다. 제너릭이란건 자세히 뭘까
✅ 제네릭이란?
- 하나의 클래스나 메서드가 다양한 데이터 타입을 처리할 수 있도록 해주는 기능
- 코드를 타입에 의존하지 않고 작성하고, 나중에 사용할 때 구체적인 타입을 지정함
- 성능이 좋고 타입 안정성도 보장됨 (컴파일 타임에 타입 검사)
🔍 예시 (제네릭 없이 작성한 코드)
ArrayList list = new ArrayList();
list.Add(1); // int
list.Add("hello"); // string
int num = (int)list[0]; // 형변환 필요 (박싱/언박싱)
- 타입이 object이기 때문에 어떤 값이든 넣을 수 있지만,
- 형변환이 필요하고, 런타임 오류가 발생할 수 있음
✅ 제네릭 사용 예시
List<int> intList = new List<int>();
intList.Add(1);
intList.Add(2);
int num = intList[0]; // 형변환 필요 없음!
- List<int>는 int형만 저장 가능
- 컴파일 타임에 타입 검사 가능 → 오류 예방
- 형변환이 없어져서 성능도 좋아짐
🔧 직접 만든 제네릭 클래스 예시
public class Box<T> // T는 타입 매개변수 (Type Parameter)
{
public T item;
public void SetItem(T value)
{
item = value;
}
public T GetItem()
{
return item;
}
}
💡 왜 제네릭을 사용할까?
이유설명
코드 재사용성 | 하나의 코드로 다양한 타입 처리 가능 |
타입 안정성 | 컴파일 타임에 타입 검사 가능 |
성능 향상 | 박싱/언박싱 제거로 메모리/속도 효율적 |
흠..GPT가 BOX<T>라는 예시를 줬는데 이 <T>가 뭔지 정확히 모르겠다 잘 사용하지 않기도 했고. 그래서 물어봤다.
✅ Box<T>란?
📦 정의
- Box<T>는 "어떤 타입 T의 데이터를 담을 수 있는 상자"를 의미하는 클래스야.
- 여기서 T는 타입 매개변수(Type Parameter) 로, 사용할 때 구체적인 타입으로 대체돼.
public class Box<T>
{
public T item;
public void SetItem(T value)
{
item = value;
}
public T GetItem()
{
return item;
}
}
✅ 핵심 개념
용어설명
T | 타입 매개변수. 보통 T(Type), K(Key), V(Value) 같은 문자로 씀 |
Box<T> | T라는 타입을 나중에 지정할 수 있는 클래스 |
재사용성 | Box<int>, Box<string> 등으로 여러 타입에 재사용 가능 |
✅ 실제 사용 예
Box<int> intBox = new Box<int>();
intBox.SetItem(10); // int 타입 사용
Console.WriteLine(intBox.GetItem()); // 출력: 10
Box<string> strBox = new Box<string>();
strBox.SetItem("hello"); // string 타입 사용
Console.WriteLine(strBox.GetItem()); // 출력: hello
- Box<int>라고 선언하면 T는 int로 바뀌고,
- Box<string>이라고 하면 T는 string으로 바뀌는 거야.
✅ 왜 이렇게 쓰는 걸까?
- 타입마다 클래스를 일일이 만들 필요 없음
- 형변환 없이 안전하게 값 사용 가능
- 컴파일 시 타입 검사 가능 → 오류 예방
✅ Box<T>를 실생활에 비유하면?
- Box<T>는 "어떤 물건이든 담을 수 있는 상자"
- 사용할 때 "이 상자에는 **책(int)**만 담을 거야", 혹은 "**편지(string)**만 담을 거야" 하고 말해주는 느낌이야.
흠..일단 그럼 T는 다양한 형식을 받을 수 있지만 그 중에 1종류만 받는다는 느낌이 강하다 생각했다 그러면 <T>라면 정말 어떤 타입이 올지 몰라서 대기하는 느낌이고 평소에 게임을 만든다면 <int>등등 정확한 타입을 써서 정의내리는게 더 깔끔할듯하다.
✅ 왜 Box<int> 같은 걸 쓸까?
1. 타입별 클래스를 중복 작성할 필요 없게 해줌
❌ 제네릭 없이 하면:
public class IntBox {
public int item;
}
public class StringBox {
public string item;
}
- int, string, float… 다 다른 클래스를 만들어야 해.
- 유지보수 힘듦 + 코드 중복
✅ 제네릭을 쓰면:
public class Box<T> {
public T item;
}
- 한 번만 만들면 어떤 타입이든 다 쓸 수 있어: Box<int>, Box<string>, Box<float>...
2. 타입 안정성과 성능을 동시에 보장
❌ object 기반이면:
public class ObjectBox {
public object item;
}
ObjectBox box = new ObjectBox();
box.item = 10;
int value = (int)box.item; // 형변환 필요 → 실수로 string을 넣었다면? → 런타임 오류
- object는 어떤 타입이든 담을 수 있지만 형변환 필요하고, 런타임 오류 가능성 있음
✅ 제네릭 쓰면:
Box<int> box = new Box<int>();
box.item = 10;
int value = box.item; // 형변환 없음! 타입 안전!
- 컴파일 타임에 타입 검사
- 형변환 없어져서 성능도 더 좋음
3. 자료구조를 만들 때 필수
예를 들어, 우리가 직접 Stack, Queue, List 같은 자료구조를 만든다고 해보자.
public class MyStack<T> {
private List<T> data = new List<T>();
public void Push(T item) {
data.Add(item);
}
public T Pop() {
T item = data[data.Count - 1];
data.RemoveAt(data.Count - 1);
return item;
}
}
📌 결론
Box<int>는 그냥 "정수를 담는 상자"고, 제네릭을 사용함으로써 우리는
- 중복 코드 없이
- 타입 안정성 확보하면서
- 다양한 타입에 대해 유연하게 대응 가능한 클래스를 만들 수 있어.
음.. 그렇다고한다 확실히 제네릭을 쓰는게 훨씬 효율적이고 형변환도 필요없으니 좋은듯하다.
반응형
'C#' 카테고리의 다른 글
C# - 자료구조 (0) | 2025.07.02 |
---|---|
C# - 해쉬셋(HashSet) vs Dictionary(HashTable) (1) | 2025.07.02 |
C# - .NET Runtime (0) | 2025.07.01 |
C# - 캡슐화 (1) | 2025.07.01 |
C# - 박싱/언박싱과 박싱/언박싱&형변환 차이 (0) | 2025.07.01 |