C# の Dictionary 同士を簡単にマージする方法

## まず方法から...

C# の`Dictionary<TKey,TValue>`同士をマージするコードを書く必要があり、どうせなら`for`文ぐるぐる、`ContainsKey`で重複チェックして...より LINQ でスマートに書きたいと思い調べたのでまとめます。

```cs
var dic1 = new Dictionary<string, int> {
  ["oarange"] = 100,
  ["apple"] = 150,
};
var dic2 = new Dictionary<string, int> {
  ["apple"] = 180,
  ["banana"] = 200,
};

var merged = dic1.Concat(dic2)
  .GroupBy(
    pair => pair.Key,
    (_, pairs) => pairs.First()
  ).ToDictionary(
    pair => pair.Key,
    pair => pair.Value
  );

foreach (var (k, v) in merged) {
  Console.WriteLine($"key: {k}, value: {v}");
}
```

```:結果
key: oarange, value: 100
key: apple, value: 150
key: banana, value: 200
```



以下は上記のコードの説明です。
興味があれば読んでいただけると嬉しいです。:smiley:

### 追記

コメントいただいた以下の方法の方がシンプルです。

```cs
var merged = dic1
  .Concat(dic2.Where(pair =>
    !dic1.ContainsKey(pair.Key))
  ).ToDictionary(
    pair => pair.Key,
    pair => pair.Value
  );
```

## 説明

型を明示すると以下のようになります。

```cs
// マージする Dictionary
var dict1 = new Dictionary<string, int> {
  ["oarange"] = 100,
  ["apple"] = 150,
};
var dict2 = new Dictionary<string, int> {
  ["apple"] = 180,
  ["banana"] = 200,
};

// Dictionary 同士を連結する
IEnumerable<KeyValuePair<string, int>> concated = dict1.Concat(dict2);

// キーが重複する KeyValuePair を削除する
IEnumerable<KeyValuePair<string, int>> pairs = concated.GroupBy(
  (KeyValuePair<string, int> pair) => pair.Key,
  (string key, IEnumerable<KeyValuePair<string, int>> keyValues) => keyValues.First()
);

// KeyValuePair のシーケンスを Dictionary 化する
Dictionary<string, int> mergedDict = pairs.ToDictionary(
  (KeyValuePair<string, int> pair) => pair.Key,
  (KeyValuePair<string, int> pair) => pair.Value
);
```

### `Dictionary`同士を連結

```cs
IEnumerable<KeyValuePair<string, int>> concated = dict1.Concat(dict2);
```

`Concat`でなく`Union`でも連結できます。
ただし`Union`の重複判定はキーでなく`GetHashCode`で得られれるハッシュ値と`Equals`で行われます。
つまり、和集合にはならず`Concat`と同様に`ToDictionary`の前に重複を弾く必要があります。
`Union`の第二引数に`IEqualityComparer<Dictionary<string, int>`を実装したクラスを渡せば重複を弾けますが面倒なのでやりません。

### キーが重複する`KeyValuePair`の削除

```cs
IEnumerable<KeyValuePair<string, int>> pairs = concated.GroupBy(
  (KeyValuePair<string, int> pair) => pair.Key,
  (string key, IEnumerable<KeyValuePair<string, int>> keyValues) => keyValues.First()
);
```

`ToDictionary`はキーが重複すると例外をスローするのでキーが重複する`KeyValuePair`を取り除く必要があります。
`GroupBy`の第二引数に`Func<TKey,IEnumerable<TElement>,TResult>`を渡すと各グループから結果値を作成することができます。
`KeyValuePair`を返しているため結果が`IEnumerable<IGrouping<string, int>>`でなく`IEnumerable<KeyValuePair<string, int>>`になっています。

#### `Dictionary`同士のキーが重複する場合、どちらを優先するか

`keyValues.First()`では`dict1`が優先され`keyValues.Last()`にすると`dict2`の値で上書きできます。

```cs
concated.GroupBy(
  pair => pair.Key,
  keyValues => keyValues.Last() // First -> Last
);
```

```cs
var dict1 = new Dictionary<string, int> {
  ["oarange"] = 100,
  ["apple"] = 150, // <- 重複
};
var dict2 = new Dictionary<string, int> {
  ["apple"] = 180, // <- 重複
  ["banana"] = 200,
};
```

```:keyValues.Firstにした場合の結果
key: oarange, value: 100
key: apple, value: 150  <- dict1 が優先
key: banana, value: 200
```

```:keyValues.Lastにした場合の結果
key: oarange, value: 100
key: apple, value: 180  <- dict2 が優先
key: banana, value: 200
```

## `KeyValuePair`のシーケンスを`Dictionary`化

```cs
Dictionary<string, int> mergedDict = pairs.ToDictionary(
  (KeyValuePair<string, int> pair) => pair.Key,
  (KeyValuePair<string, int> pair) => pair.Value
);
```

`ToDictionary`は第一引数でキーを、第二引数にバリューをそれぞれラムダ式で指定できます。

## 拡張メソッド

`for`文回して～`ContainsKey`で重複判定して～と比べると`Concat` > `GroupBy` > `ToDicitonary`は短くなりますが、何度も書くのは面倒なので拡張メソッドを定義しておきます。

```cs
public static class IDictionaryExtentions {
  public static IDictionary<TKey, TValue> Merge<TKey, TValue>(
    this IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second
  ) => first.Concat(second)
    .GroupBy(pair => pair.Key, (_, pairs) => pairs.First())
    .ToDictionary(pair => pair.Key, pair => pair.Value);

  public static IDictionary<TKey, TValue> Update<TKey, TValue>(
    this IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second
  ) => first.Concat(second)
    .GroupBy(pair => pair.Key, (_, pairs) => pairs.Last())
    .ToDictionary(pair => pair.Key, pair => pair.Value);
}
```

`Merge`は重複するキーがある場合、呼出し側の`Dictionary`を優先します。
`Update`は重複キーがある場合、引数で与えた`Dictionary`を優先します。

```cs
var dic1 = new Dictionary<string, int> {
  ["oarange"] = 100,
  ["apple"] = 150,
};
var dic2 = new Dictionary<string, int> {
  ["apple"] = 180,
  ["banana"] = 200,
};

var merged = dict1.Merge(dict2);
foreach (var (k, v) in merged) {
  Console.WriteLine($"key: {k}, value: {v}");
}

/*
  key: oarange, value: 100
  key: apple, value: 150  <- 上書きされない
  key: banana, value: 200
 */

var updated = dict1.Update(dict2);
foreach (var (k, v) in updated) {
  Console.WriteLine($"key: {k}, value: {v}");
}

/*
  key: oarange, value: 100
  key: apple, value: 180  <- 上書きする
  key: banana, value: 200
 */
```