CodeGym /행동 /C# SELF /사전: Dictionary<TKey, T...

사전: Dictionary<TKey, TValue>

C# SELF
레벨 27 , 레슨 3
사용 가능

1. 소개

상상해봐, 네가 작은 가게 사장이라고 치자. 그리고 상품 목록이 있어. 각 상품은 고유한 상품코드(예: ART-001, ART-002)가 있고, 당연히 이름, 가격, 재고수량도 있지.

만약 이걸 List<T>에 저장한다면, 여기서 T는 예를 들어 우리가 나중에 만들 Product 클래스라고 해보자. 그럼 ART-005 상품을 찾으려면 리스트를 전부 뒤져야 해:
"이거 ART-001이야? 아니네. 이건 ART-002? 아니고... 아, 이게 ART-005네! 찾았다!"
상품이 10개면 별 문제 없지. 근데 10,000개? 100,000개? 하나 찾으려면 엄청 오래 걸릴 거야. 손님이 자기 과자 한 봉지 찾으려고 한참 기다려야 한다면, 그건 좀 아니지!

우리는 고유한 상품코드를 알면 바로 그 상품으로 점프할 수 있는 방법이 필요해. 전부 다 뒤지지 않고 말이야. 즉, 뭔가 "키"가 있어서 바로 "값"을 가리키는 구조가 필요하지.

Dictionary랑 친해지기

여기서 Dictionary<TKey, TValue>가 등장! 이건 그냥 리스트가 아니라, 똑똑한 전화번호부라고 생각하면 돼. 보통 전화번호부에서 사람 이름(키)으로 전화번호(값)를 찾잖아? "A"로 넘기고, "Alexey" 찾으면 바로 번호가 나오지. 전부 다 넘길 필요 없어.

Dictionary(영어로 "사전"이라는 뜻)도 똑같이 "키-값" 쌍으로 데이터를 저장해.

  • 키(TKey): 각 요소의 고유 식별자야. 전화번호부의 이름이나 상품코드 같은 거지. 이 키로 원하는 값을 찾을 수 있어. 키는 유일해야 해. 이미 있는 키로 또 추가하려고 하면 Dictionary가 절대 허락 안 해.
  • 값(TValue): 네가 저장하고 싶은 데이터야. 전화번호, 상품 가격, 용어 설명 등 뭐든 가능!

TKeyTValue<TKey, TValue>처럼 꺾쇠 안에 들어가는데, Dictionary제네릭(Generic) 컬렉션이란 뜻이야. 네가 직접 키 타입, 값 타입을 정하는 거지. 키는 string(이름, 상품코드), int(유저 ID), 아니면 네가 만든 클래스도 돼. 값도 int, string, double 또는 객체도 가능.

장점? 바로 접근 가능! 내부적으로(과학적으로 말하면 해시테이블 구조인데, 지금은 몰라도 돼) Dictionary는 키로 값을 엄청 빠르게 찾을 수 있어. 요소가 10개든 백만 개든 상관없이! 거대한 도서관에서 "나 C# 책 필요해" 하면 바로 위치 알려주는 슈퍼 인덱스 같은 거지.

바로 실전! 우리 프로젝트를 발전시켜서, C# 용어 미니 사전을 만들어보자. 새로운 개념 외울 때도 도움될 거야.

2. 문법 기초: 사전 만들기

항상 그렇듯이, 변수 선언부터 시작! 이번엔 타입을 두 개, 키 타입(TKey)이랑 값 타입(TValue)을 꼭 써줘야 해:

// 간단한 사전: 키 - string(로그인), 값 - string(email)
Dictionary<string, string> userEmails = new Dictionary<string, string>();

// 또는 var로 더 짧게
var userEmails = new Dictionary<string, string>();

왜 타입 두 개를 꼭 써야 할까?
C#은 엄격한 타입 언어라서, 사전이 어떤 타입의 키와 값을 쓸지 알아야 해.

요소 추가하기

새로운 "키-값" 쌍을 추가하려면 Add 메서드를 써. 키는 무조건 유일해야 해!

userEmails.Add("vasya", "vasya@example.com");
userEmails.Add("petya", "petya@gmail.com");

같은 키로 또 추가하려고 하면 사전이 삐지고 예외를 던질 거야.

키로 값 가져오기

사전의 핵심은 키로 값 바로 찾는 거지:

string email = userEmails["vasya"];
Console.WriteLine(email); // vasya@example.com

없는 키로 접근하면 프로그램이 바로 소리 지를 거야(KeyNotFoundException 예외). 안전하게 하려면 곧 키 존재 확인하는 방법도 배울 거야.

키로 값 바꾸기

이미 있는 키면 그냥 새 값 할당하면 돼:

userEmails["vasya"] = "vasya@newmail.ru"; // 이제 vasya의 email이 바뀜

키가 없으면? 이렇게 할당하면 새 요소가 생겨.

유저 예제

우리 연습용 앱을 좀 더 발전시켜보자. 원래 List<string> tasks;로 할 일 목록을 저장했었지. 이제 "로그인" 기능을 추가한다고 치자: 각 유저마다 email이 필요해.

이렇게 만들 수 있어:

// UserId는 string, Email도 string
var users = new Dictionary<string, string>();
users.Add("admin", "admin@myapp.com");
users.Add("alice", "alice@wonderland.com");
users.Add("bob", "bob@builder.com");

이제 언제든 로그인으로 유저 email을 바로 찾을 수 있어:

Console.WriteLine(users["alice"]); // => alice@wonderland.com

3. Dictionary의 주요 메서드와 속성

메서드/속성 설명
Add(key, value)
새로운 "키-값" 쌍을 추가해.
Remove(key)
키로 요소를 삭제해.
ContainsKey(key)
해당 키가 있는지 확인해.
ContainsValue(value)
해당 값이 있는지 확인(느림!).
TryGetValue(key, out val)
예외 없이 안전하게 키로 값을 가져와.
Count
사전에 있는 "키-값" 쌍의 개수.
Keys
모든 키 컬렉션.
Values
모든 값 컬렉션.

키 존재 확인하기

가장 흔하고 안전한 방법은 먼저 키가 있는지 확인하는 거야:

if (users.ContainsKey("dasha"))
{
    Console.WriteLine(users["dasha"]);
}
else
{
    Console.WriteLine("유저 dasha를 찾을 수 없어!");
}

안전한 방법: TryGetValue

TryGetValue 메서드를 쓰면 예외 없이 값 가져올 수 있어:

if (users.TryGetValue("bob", out string email))
{
    Console.WriteLine($"Bob의 메일: {email}");
}
else
{
    Console.WriteLine("Bob을 찾을 수 없어!");
}

이건 진짜 좋은 습관이고, 면접에서도 자주 물어보니까 꼭 익혀둬! 그리고 ContainsKey + 인덱스 접근보다 더 빨라.

4. 사전 순회: foreach 루프

모든 쌍을 돌고 싶으면 foreach 루프를 써. 각 요소는 KeyValuePair<TKey, TValue> 타입이야:

foreach (var pair in users)
{
    Console.WriteLine($"로그인: {pair.Key}, Email: {pair.Value}");
}

아니면 더 멋지게 이렇게도 가능:

foreach (var (login, email) in users)
{
    Console.WriteLine($"{login}: {email}");
}
// 이 문법은 튜플 디컨스트럭션(C# 7+) 덕분에 가능해.

5. 값 삭제와 수정

로그인으로 유저 삭제는 이렇게:

users.Remove("alice");

키가 없으면 false를 반환해. 예외 걱정 없이 삭제 시도해도 돼.

유저 email 바꾸기:

users["bob"] = "bob@constructor.com";

키가 없으면 새 쌍이 추가돼!

6. KeysValues 속성

로그인(키) 목록이나 email(값) 목록만 필요하면 KeysValues 컬렉션을 써:

foreach (string login in users.Keys)
{
    Console.WriteLine("로그인: " + login);
}

foreach (string email in users.Values)
{
    Console.WriteLine("Email: " + email);
}

7. 사전의 중요한 포인트

키는 유일해야 해

같은 키 두 번 추가는 안 돼. 시도하면 예외 나와. 이런 유일성이 데이터 안전을 보장해: 한 유저가 이메일 두 개를 가질 수 없어(한 레코드에 하나만).

키는 null이 될 수 없어(string의 경우)

문자열 키에 null을 넣으려 하면 에러(ArgumentNullException)가 나. 키가 없다면, 데이터 로직에 뭔가 문제 있는 걸 수도 있으니 한 번 더 생각해봐.

왜 사전 검색이 이렇게 빠를까?

Dictionary는 내부적으로 해시테이블로 동작해. 즉, 키로 값을 찾을 때 전부 다 뒤지는 게 아니라, 해시 함수를 계산해서 거의 바로 해당 위치로 점프하는 거지.

키로 쓸 수 있는 건 뭐가 있을까?

  • EqualsGetHashCode()가 제대로 구현된 타입이면 뭐든 가능.
  • 보통 string, int, Guid 또는 네가 만든 타입(이때 Equals/GetHashCode 오버라이드 조심! 안 그러면 버그 생길 수 있음).

8. 앱에 사전 추가하기

우리 미니 ToDo 앱에 유저 사전을 추가하고, 로그인으로 email 찾기(에러 처리 포함) 기능을 만들어보자:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // 유저 사전: 로그인 => email
        var users = new Dictionary<string, string>
        {
            { "admin", "admin@myapp.com" },
            { "alice", "alice@wonderland.com" },
            { "bob", "bob@builder.com" }
        };

        Console.WriteLine("이메일을 찾을 유저 로그인 입력:");
        string login = Console.ReadLine();

        // 안전하게 email 찾기
        if (users.TryGetValue(login, out string email))
        {
            Console.WriteLine($"{login} 유저의 Email: {email}");
        }
        else
        {
            Console.WriteLine($"{login} 유저를 찾을 수 없어.");
        }

        // 모든 유저 순회
        Console.WriteLine("\n모든 유저 목록:");
        foreach (var pair in users)
        {
            Console.WriteLine($"{pair.Key} => {pair.Value}");
        }
    }
}

9. 초보자들이 자주 하는 실수와 함정

가끔 이런 식으로 하고 싶을 때가 있어:

// 키가 없으면 괜찮겠지? 라고 생각하지만
string value = users["nonexistent"]; // 뿅! KeyNotFoundException!

잊지 마: 키가 확실히 있는지 모르면 항상 (ContainsKey 또는 TryGetValue)로 확인해.

그리고 값 순회는 값의 유일성을 보장하지 않아! 두 로그인에 같은 email이 들어갈 수도 있어(값의 유일성은 자동 보장 안 됨).

메서드도 헷갈릴 수 있어 — 예를 들어 값으로 삭제하려고 하면:

users.Remove("bob@builder.com"); // 삭제 안 됨! 키를 기대하는 거야, 값이 아니라.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION