1. 들어가기
상상해봐: 너가 고양이 연구 비밀 연구소에 취직했어. 너한테 미션이 떨어졌지 — 고양이 이름으로 나이, 색깔, 꼬리 길이를 빠르게 알 수 있는 전자 딕셔너리를 만들어야 해. 이미 준비된 솔루션이 있어 — 바로 Dictionary<string, Cat>야. 근데 문제는, 나중에 상사가 말하길, 어떤 희귀 품종은 추가 순서를 기억해야 하고, 가끔은 특이한 방식으로 삭제도 해야 한대.
만약 너가 "고양이 딕셔너리"라면 뭐든 받을 수 있는 메서드를 짜야 한다면 어떨까? 구체적으로 어떤 클래스인지는 상관없고, 중요한 건 기본적인 "키-값" 연산만 지원하면 돼. 여기서 IDictionary<TKey, TValue> 인터페이스가 등장하는 거지.
IDictionary<TKey, TValue>는 .NET에서 딕셔너리가 뭔지 정의하는 공통 계약이야. 이건 구체적인 클래스가 아니라, 인터페이스 — 즉, 진짜 딕셔너리라면 반드시 따라야 하는 규칙 모음이야:
- 키로 빠르게 찾기
- "키-값" 쌍 추가, 삭제
- 모든 쌍 순회
- 키 존재 여부 확인
만약 중세에 Java가 있었다면, 기사들은 용과 싸울 때 칼 대신 키와 값을 휘둘렀을 거야. 그리고 C# 개발자들은 그냥 IDictionary<TKey, TValue> 구현했겠지 — 그러면 용(=네 코드)은 바로 끝장났을 거고.
2. IDictionary 인터페이스 표
이 인터페이스의 주요 "약속"(멤버)들을 한 번 보자:
| 인터페이스 멤버 | 타입 | 설명 |
|---|---|---|
|
속성 | 인덱서. 지정한 키에 연결된 값을 가져오거나 설정할 수 있어. 읽을 때: 키가 없으면 KeyNotFoundException이 터져. 쓸 때: 키가 없으면 새 쌍을 추가하고, 있으면 값을 업데이트해. 중요: 이게 딕셔너리 쓸 때 제일 많이 쓰는 방식이야. |
|
속성 | 딕셔너리 안의 모든 키를 담은 컬렉션을 반환해. 키만 순회할 때 써. |
|
속성 | 딕셔너리 안의 모든 값을 담은 컬렉션을 반환해. 값만 순회할 때 써. |
|
메서드 | 지정한 키와 값을 딕셔너리에 추가해. 이미 키가 있으면 ArgumentException이 터져. |
|
메서드 | 딕셔너리에 해당 키가 있는지 확인해. true면 찾은 거고, 아니면 false야. 인덱서로 없는 키에 접근해서 에러나는 걸 막을 때 아주 유용해. |
|
메서드 | 지정한 키의 요소를 딕셔너리에서 삭제해. true면 성공적으로 찾고 삭제한 거고, 아니면 false(예: 키가 없을 때). |
|
메서드 | 지정한 키에 연결된 값을 가져와. 이건 안전하게 값을 얻는 방법이야, 키가 있는지 확실하지 않을 때. true면 키를 찾았고, value에 값이 들어가. 아니면 false고, value는 TValue의 기본값이야. 이 메서드는 예외를 던지지 않아서 실제 코드에서 엄청 많이 써. |
3. IDictionary<TKey, TValue> 인터페이스의 주요 멤버
이제 제일 중요한 부분! IDictionary<TKey, TValue> 인터페이스 안에 뭐가 들어있는지 보고, 이 "계약"을 네 코드에서 어떻게 쓰는지 배워보자.
public interface IDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>
{
TValue this[TKey key] { get; set; } // 키로 접근하는 인덱서
ICollection<TKey> Keys { get; }
ICollection<TValue> Values { get; }
void Add(TKey key, TValue value); // 새 쌍 추가(키가 없어야 함)
bool ContainsKey(TKey key); // 이런 키가 있나?
bool Remove(TKey key); // 키로 삭제
bool TryGetValue(TKey key, out TValue value); // 안전하게 값 얻기
}
주요 포인트만 정리해보자:
인덱서 [key]
키로 값을 가져오거나 설정할 수 있어:
dictionary["Барсик"] = new Cat("Барсик", 2);
Keys와 Values 속성
딕셔너리의 모든 키 또는 모든 값을 컬렉션으로 얻을 수 있어.
foreach (var name in dictionary.Keys)
{
Console.WriteLine(name);
}
Add, Remove, ContainsKey, TryGetValue 메서드
- Add(key, value) — 새 쌍 추가
- Remove(key) — 키로 삭제
- ContainsKey(key) — 키가 있는지 확인
- TryGetValue(key, out value) — 안전하게 값 얻기(키 없으면 예외 안 남)
4. IDictionary<TKey, TValue> 실전 사용법
범용 메서드
예를 들어, 너가 딕셔너리랑 작업하는 함수를 짜는데, 내부적으로 어떤 클래스인지 신경 쓰고 싶지 않아: 그냥 Dictionary일 수도 있고, SortedDictionary일 수도 있고, 누가 자기만의 "크립토딕셔너리"를 만들었을 수도 있지.
파라미터 타입을 IDictionary<TKey, TValue>로 선언하면 돼:
static void PrintDictionary<TKey, TValue>(IDictionary<TKey, TValue> someDictionary)
{
foreach (var pair in someDictionary)
{
Console.WriteLine($"{pair.Key}: {pair.Value}");
}
}
이제 네 메서드는 어떤 딕셔너리든 받을 수 있어! 내부적으로 뭐가 들어있든 상관없지 — 심지어 값이 암호화되는 딕셔너리여도, 네가 그렇게 재밌게 살고 있다면 말이야.
IDictionary로 파라미터 전달하기
예를 들어, 너가 고양이 관리 앱을 만들고 있는데, 네 메서드가 모든 종류의 설정 딕셔너리랑 동작하길 원해. 특정 클래스에만 묶이고 싶지 않다면 이렇게 하면 돼:
void SetCatParameters(IDictionary<string, string> parameters)
{
if (parameters.ContainsKey("color"))
{
Console.WriteLine($"고양이 색칠: {parameters["color"]}");
}
}
하나의 시그니처 — 여러 구현
표로 한 번 정리해볼게:
| 컬렉션 클래스 | IDictionary<TKey,TValue> 구현 | 특징 |
|---|---|---|
|
✅ 예 | 빠른 검색, 키 순서는 랜덤 |
|
✅ 예 | 키가 자동으로 정렬됨 |
|
✅ 예 | 키 정렬, 메모리 절약형 |
| (인터페이스 구현한 네 클래스) | ✅ 예 | 네 마음대로 로직, 하지만 계약은 꼭 지켜야 함 |
5. 다른 컬렉션 인터페이스와의 관계
진짜 덕후들을 위해: IDictionary<TKey, TValue> 인터페이스는 ICollection<KeyValuePair<TKey, TValue>>랑 IEnumerable<KeyValuePair<TKey, TValue>>를 상속해. 즉, 모든 딕셔너리는:
- foreach로 키-값 쌍 순회 가능,
- 컬렉션 메서드로 쌍 추가/삭제 가능,
- 요소 개수 얻기 가능.
foreach (var entry in myDictionary)
{
Console.WriteLine($"{entry.Key} => {entry.Value}");
}
6. 특징과 흔한 실수
인덱서 사용
초보들이 제일 많이 하는 실수: 없는 키로 요소를 가져오려고 하면 KeyNotFoundException이 터져.
var value = myDictionary["НемаТакогоКлюча"]; // Boom!
그래서 항상 TryGetValue를 쓰는 게 좋아:
if (myDictionary.TryGetValue("Мурзик", out var cat))
{
Console.WriteLine($"고양이 찾음: {cat}");
}
else
{
Console.WriteLine($"고양이 못 찾았어!");
}
이미 있는 키 추가
이미 있는 키로 Add를 호출하면 ArgumentException이 터져. "추가 또는 업데이트"를 원하면 인덱서를 써:
// 없으면 추가, 있으면 업데이트
myDictionary["Мурка"] = new Cat("Мурка", 5);
순회와 수정
foreach 루프에서 딕셔너리를 직접 수정(예: 삭제)하려고 하면 예외가 나. 뭔가를 삭제하고 싶으면, 먼저 삭제할 키 목록을 따로 모아서, 그 다음에 따로 삭제해:
// 안전하게 요소 삭제하는 방법
var keysToRemove = new List<string>();
foreach (var pair in myDictionary)
{
if (pair.Value.Age > 10) // 삭제 조건
{
keysToRemove.Add(pair.Key);
}
}
foreach (var key in keysToRemove)
{
myDictionary.Remove(key);
}
GO TO FULL VERSION