CodeGym /コース /C# SELF /辞書: Dictionary<TKey, TValue>

辞書: 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個なら問題ないけど、1万個とか10万個あったら?毎回探すのにめっちゃ時間かかるし、お客さんもお気に入りのお菓子を見つけるまでずっと待たされる。ダメだよね!

だから、ユニークな品番さえ分かれば一発でその商品にアクセスできる方法が必要なんだ。つまり、「キー」で直接「値」を指し示す仕組みが欲しいってこと。

Dictionaryとの出会い

そこで登場するのがDictionary<TKey, TValue>!これはただのリストじゃなくて、めっちゃ賢い電話帳みたいなもの。普通の電話帳なら、人の名前(キー)で電話番号(値)を探すよね。「A」のページを開いて「アキラ」を探せば、すぐ番号が分かる。全部の番号を順番に見る必要はない。

Dictionary(英語で「辞書」って意味)は、まさに「キーと値」のペアでデータを保存するんだ。

  • キー(TKey):各要素のユニークなID。電話帳の名前や商品の品番みたいなもの。このキーで値を探す。キーはユニークじゃないとダメ。同じキーを追加しようとすると、Dictionaryは許してくれない。
  • 値(TValue):保存したいデータ本体。電話番号、商品の値段、用語の説明…何でもOK!

TKeyとTValueは、<TKey, TValue>の山カッコの中に書くけど、これはDictionaryがジェネリック(Generic)コレクションだから。どんな型をキーや値に使うかは自分で決められる。キーはstring(名前や品番)、int(ユーザーID)、自作クラスでもOK。値もint、string、double、オブジェクト何でもアリ。

メリット?即アクセス! 特別な内部構造(ハッシュテーブルってやつ。詳しくは今は知らなくてOK)のおかげで、Dictionaryはキーで値を探すのがめっちゃ速い。要素が10個でも100万個でも関係なし。巨大な図書館のスーパーインデックスみたいな感じ。「C#の本ちょうだい」って言ったら一瞬で場所を教えてくれる。全部の棚を探す必要なし!

じゃあ早速やってみよう!これから「C#用語ミニ辞書」を作りながら、実際に使い方を覚えていくよ。

2. 基本の文法:辞書を作る

まずはいつも通り変数を宣言。でも今回は型を2つ指定するよ。キーの型(TKey)と値の型(TValue):

// シンプルな辞書:キーはstring(ログイン名)、値もstring(メールアドレス)
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)になる。安全に使う方法は後で説明するね。

キーで値を変更

そのキーが既にあるなら、新しい値を代入するだけでOK:

userEmails["vasya"] = "vasya@newmail.ru"; // これでVasyaのメールが変わる

もしそのキーがなかったら、新しい要素が追加されるよ。

ユーザーの例

ちょっとToDoアプリを進化させてみよう。今まではタスクのリスト(List<string> tasks;)だけだったけど、今度は「認証」機能を追加したい。つまり、各ユーザーにメールアドレスを持たせる。

こんな感じになるよ:

// UserIdも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");

これで、どのユーザーのメールもログイン名ですぐ分かる:

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}, メール: {pair.Value}");
}

もっとイケてる書き方もできる:

foreach (var (login, email) in users)
{
    Console.WriteLine($"{login}: {email}");
}
// この書き方はタプルの分解(C# 7+)で使えるよ。

5. 値の削除と変更

ログイン名でユーザーを削除するのは簡単:

users.Remove("alice");

そのキーがなければfalseを返す。例外の心配なしで削除できるよ。

ユーザーのメールを変更するには:

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

そのキーがなかったら新しいペアが追加される!

6. サブプロパティKeysとValues

ログイン名(キー)だけ、またはメール(値)だけ欲しい時は、KeysやValuesコレクションを使おう:

foreach (string login in users.Keys)
{
    Console.WriteLine("ログイン: " + login);
}

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

7. 辞書の大事なポイント

キーはユニークじゃないとダメ

同じキーを2つ追加することはできない。やろうとすると例外が出る。このユニーク性のおかげでデータの安全が守られる。1人のユーザーが2つのメールを持つことはできない(1レコードにつき1つだけ)。

キーはnullにできない(stringの場合)

文字列キーでnullを追加しようとするとエラー(ArgumentNullException)になる。もしキーがないなら、それはデータ設計の問題かも。

なぜ辞書の検索は速いの?

Dictionaryは「中身」でハッシュテーブルを使ってる。つまり、キーで全部を順番に探すんじゃなくて、特別な「ハッシュ関数」で一発で値の場所を計算してアクセスできるんだ。

キーに使える型は?

  • EqualsとGetHashCode()がちゃんと実装されてる型なら何でもOK。
  • 普通はstring、int、Guid、自作型もOK(ただしEquals/GetHashCodeのオーバーライドには注意。バグの元になることも)。

8. アプリに辞書を追加しよう

ToDoミニアプリにユーザー辞書を追加して、ログイン名でメールを探す機能(エラー処理付き)を作ってみよう:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // ユーザー辞書:ログイン => メール
        var users = new Dictionary<string, string>
        {
            { "admin", "admin@myapp.com" },
            { "alice", "alice@wonderland.com" },
            { "bob", "bob@builder.com" }
        };

        Console.WriteLine("メールを探したいユーザーのログイン名を入力してね:");
        string login = Console.ReadLine();

        // 安全なメール検索
        if (users.TryGetValue(login, out string email))
        {
            Console.WriteLine($"ユーザー{login}のメール: {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)を必ずチェックしよう。確実にある時以外はね。

あと、値のループは値のユニーク性を保証しない!同じメールが2つのログイン名に使われてることもある(値のユニーク性を管理しないと、こうなる)。

メソッドの使い間違いもよくある。例えば値で削除しようとするとか:

users.Remove("bob@builder.com"); // 削除されない!キーが必要で、値じゃないよ。
2
タスク
C# SELF, レベル 27, レッスン 3
ロック未解除
キーと値の存在チェック
キーと値の存在チェック
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION