はじめに
Dictionary
を使ってデータを管理していると下記のエラーに遭遇しました。
ArgumentException: An item with the same key has already been added. Key: player1
普段あまりDictionary
でエラーが発生していなかったので戸惑いましたが、
エラー文面通り単純に同じキーを2回追加しようとしたときに発生するようです。
エラー再現コード
void Start() {
Dictionary<string, int> scores = new Dictionary<string, int>();
scores.Add("player1", 100);
scores.Add("player2", 200);
// 同じキーを再度追加
scores.Add("player1", 300); // ここでエラー
}
上記コードを実行するとUnityコンソールにArgumentException
が表示されます。
上記は単純な例ですが、よくあるのは下記コードのような
複数個所や同タイミングでDictionary
へ同一データを追加する処理を走らせてしまう場合などです。
public static class SharedStore {
public static Dictionary<string, int> scores = new Dictionary<string, int>();
}
public class ProcessorA {
public void Run() {
SharedStore.scores.Add("player1", 100);
SharedStore.scores.Add("player1", 300);
}
}
public class ProcessorB {
public void Run() {
// ProcessorAでも同じキーを追加している
SharedStore.scores.Add("player1", 300);
}
}
public class Main {
static async Task Start() {
var taskA = Task.Run(() => new ProcessorA().Run());
var taskB = Task.Run(() => new ProcessorB().Run());
}
}
自分はこんな感じの処理で発生していました。
(正直この場合は根本解決した方が良いと思いますが、状況によってはそうもいかない場合があると思いますのでエラーの解決方法を記載しますね)
原因・解決法
Dictionary
はキーの重複を許しません。
Add
メソッドでは、すでに存在するキーを追加しようとすると例外を投げます。
解決法1:TryAdd
を使用する
scores.TryAdd("player1", 300);
TryAdd
メソッドは、
キーが存在しない場合のみ追加し、成功すればtrue
、失敗すればfalse
を返します。
そのため、TryAdd
はAdd
のように重複キーによる例外を発生させません。
ただし、C#7.0以降の機能であり、.NET Core 2.0 / .NET Standard 2.1 / .NET Framework 非対応なので環境によっては使用に注意が必要です。
解決法2:ContainsKey
で事前チェック
if (!scores.ContainsKey("player1") {
scores.Add("player1", 300);
}
解決法3:値の上書き
scores["player1"] = 300; // 存在すれば上書き、なければ追加
まとめ
今回のようなDictionary
で発生するArgumentException
の解決法のまとめになります。
・Dictionary
はキーの重複を許さない
・Add
は重複時に例外を投げる
・ TryAdd
/ ContainsKey
/ インデクサでの上書き で回避可能
付録
この記事を見ている方はデータ管理等でDictionary
を有効活用して開発を行っているかと思います。
そこでUnity開発において、Dictionary
の使用に関する注意点、要点などを深堀してまとめておきます。
いずれかがお役にたてば幸いです。
重複キーが発生しやすい原因・対策
同じGameObject/Script
がAwake/OnEnable/Start
の複数箇所で同じデータを登録している。
Prefab
+ Instantiate
やシーン跨ぎのシングルトン二重生成でも起こるため注意。
対策: 一意な初期化ガード(例: initialized フラグ)や DontDestroyOnLoad + シングルトン検出など。
同じハンドラが複数回呼ばれて重複追加。
対策: 購読の対称性(OnEnableで登録→OnDisableで解除)と 重複購読防止。
大文字小文字・トリム不統一で別経路から同じ意味のキーが入る。
対策: 規約統一(例: ToLowerInvariant().Trim())と IEqualityComparer 指定。
同一フレーム内に複数の Add が走ることがある。
対策: Upsert パターン(後述)や 排他制御(基本 Unity はメインスレッド前提)。
パフォーマンスに関して
var dict = new Dictionary<string, int>(capacity: 10_000);
既存値取得は TryGetValue
が例外を避けて最速。
if (scores.TryGetValue(id, out var s)) { /* use s */ }
Clear()
はサイズを保持する。大量解放後に縮めたいなら 新規インスタンスに置き換え。
データ追加時の設計観点
メソッド | 重複時の挙動 | 目的 | 使い分け |
---|---|---|---|
Add |
例外を投げる | 厳密な一意性保証 | 基本 |
TryAdd |
何もしない(false返す) | 安全な初回追加 | 競合回避・初期化 |
dict[Key] = value |
上書き or 追加 | 値の更新 | 状態更新 |
参考記事
・【C#】Dictionary.Addとインデクサーによる要素の追加で挙動がどう異なるか(重複するキーの場合にArgumentExceptionか上書き)
・【C#】Dictionaryの使い方と落とし穴