概要
C#のDictionaryは便利なクラスですが、メソッド関係はけっこう貧弱です。
様々なLINQ拡張メソッドが含まれているInteractive Extensions(Ix.NET)にもDictionary関係はありません。
Dictionaryに値が含まれているかどうかの条件分岐が絡むことが多いため、
ちょっとした操作で3行ぐらい消費してしまいます。
そこで定型的な処理をまとめた、C#のDictionaryをもっと使いやすくする拡張メソッドを紹介します。
デモ用コード
各拡張メソッドの説明の前に、デモで使用するコードについて説明します。
ソース配列の生成メソッドと式木を使ってDictionaryを見やすくコンソール出力するメソッドです。
(参考)式木を使った簡単なPrintデバッグ出力
/// <summary>
/// 元Dictionaryの生成
/// </summary>
private static Dictionary<string, int> CreateSourceDictionary()
=> new Dictionary<string, int>
{
["A"] = 10,
["B"] = 20,
};
/// <summary>
/// Dictionaryの内容をコンソール出力 ex.「sourceDict: { A = 10, B = 20, }」
/// </summary>
private static void ConsoleWriteDictionary<TKey, TValue>(Expression<Func<Dictionary<TKey, TValue>>> dictExp)
{
var memName = (dictExp.Body as MemberExpression)?.Member.Name;
var dict = dictExp.Compile().Invoke();
Console.Write($"{memName}: {{ ");
foreach (var kv in dict)
{
Console.Write($"{kv.Key} = {kv.Value}, ");
}
Console.WriteLine("}");
}
こんな感じで動きます。
Dictionary<string, int> sourceDict = CreateSourceDictionary();
Console.WriteLine("# Start DictionaryExtension Demo");
ConsoleWriteDictionary(() => sourceDict);
# Start DictionaryExtension Demo
sourceDict: { A = 10, B = 20, }
Keyがないなら~する系
GetOrDefault
これを書いたことのある人は多いのではないでしょうか。
指定したKeyがDictionaryにあれば、そのValueを返し、ないならValue型のDefault値を返します。
public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key)
=> dict.TryGetValue(key, out TValue result) ? result : default(TValue);
Console.WriteLine($"# {nameof(DictionaryExtension.GetOrDefault)}");
Console.WriteLine($"exist value = {sourceDict.GetOrDefault("A")}");
Console.WriteLine($"unexist value = {sourceDict.GetOrDefault("C")}");
ConsoleWriteDictionary(() => sourceDict);
# GetOrDefault
exist value = 10
unexist value = 0
sourceDict: { A = 10, B = 20, }
TryAdd
指定したKeyがDictionaryにあれば、何もせず、ないなら指定したValueと共に追加します。
追加を実行したかを返します。
public static bool TryAddNew<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key) where TValue : new()
=> dict.TryAdd(key, _ => new TValue());
public static bool TryAddDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key)
=> dict.TryAdd(key, default(TValue));
public static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue addValue)
{
bool canAdd = !dict.ContainsKey(key);
if (canAdd)
dict.Add(key, addValue);
return canAdd;
}
public static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> addValueFactory)
{
bool canAdd = !dict.ContainsKey(key);
if (canAdd)
dict.Add(key, addValueFactory(key));
return canAdd;
}
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.TryAdd)}");
Console.WriteLine($"A result = {sourceDict.TryAdd("A", 1000)}");//これのみ無視される
Console.WriteLine($"C result = {sourceDict.TryAdd("C", 3000)}");
Console.WriteLine($"D result = {sourceDict.TryAdd("D", _ => 4000)}");
Console.WriteLine($"E result = {sourceDict.TryAddNew("E")}");
Console.WriteLine($"F result = {sourceDict.TryAddDefault("F")}");
ConsoleWriteDictionary(() => sourceDict);
# TryAdd
A result = False
C result = True
D result = True
E result = True
F result = True
sourceDict: { A = 10, B = 20, C = 3000, D = 4000, E = 0, F = 0, }
ちょっと名前の違う2つのメソッドの違い
-
TryAddNew
はValueがnew TValue()
-
TryAddDefault
はValueがdefault(TValue)
TryAdd
の2つのオーバーロードの使い分け
- Valueが参照型や軽量な値型の場合は
TryAdd(..., TValue addValue)
- Valueを毎回生成したい場合や、生成処理をできるだけしたくないのであれば
TryAdd(..., Func<TKey, TValue> addValueFactory)
Addやインデックスでの入力との違いは指定したKeyがDictionaryにある時の挙動です。
Addで例外発生するのがいやな時に使うことが多いと思います。
種類 | Keyなし | Keyあり |
---|---|---|
[key] = value | keyとvalueの追加 | valueの上書き |
Add(key, value) | keyとvalueの追加 | 例外発生 |
TryAdd(key, value) | keyとvalueの追加 | 変化なし |
GetOrAdd
命名がイマイチ気に入ってない。。。
指定したKeyがDictionaryにあれば、そのValueを返し、ないなら値を追加した上で返します。
キャッシュとしてDictionaryを使うときなどに有用です。
TryAddと値の取得を組み合わせたものです。
public static TValue GetOrAddNew<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key) where TValue : new()
{
dict.TryAddNew(key);
return dict[key];
}
public static TValue GetOrAddDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key)
{
dict.TryAddDefault(key);
return dict[key];
}
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue addValue)
{
dict.TryAdd(key, addValue);
return dict[key];
}
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> valueFactory)
{
dict.TryAdd(key, valueFactory);
return dict[key];
}
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.GetOrAdd)}");
Console.WriteLine($"exist value = {sourceDict.GetOrAdd("A", 10000)}");//これのみDictionaryが変化しない
Console.WriteLine($"unexist value = {sourceDict.GetOrAdd("C", 30000)}");
Console.WriteLine($"unexist value = {sourceDict.GetOrAdd("D", _ => 40000)}");
Console.WriteLine($"unexist value = {sourceDict.GetOrAddNew("E")}");
Console.WriteLine($"unexist value = {sourceDict.GetOrAddDefault("F")}");
ConsoleWriteDictionary(() => sourceDict);
# GetOrAdd
exist value = 10
unexist value = 30000
unexist value = 40000
unexist value = 0
unexist value = 0
sourceDict: { A = 10, B = 20, C = 30000, D = 40000, E = 0, F = 0, }
Dictionary加工系
Add(KeyValuePair)
1つのKeyValuePairを追加します。
あるDictionaryの要素をLINQなどで加工した後に、KeyValuePairのまま、別のDictionaryに追加したい時などに使用します。
public static void Add<TKey, TValue>(this IDictionary<TKey, TValue> source, KeyValuePair<TKey, TValue> addPair)
=> source.Add(addPair.Key, addPair.Value);
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.Add)}(KeyValuePair)");
sourceDict.Add(new KeyValuePair<string, int>("C", 300));
ConsoleWriteDictionary(() => sourceDict);
# Add(KeyValuePair)
sourceDict: { A = 10, B = 20, C = 300, }
AddOrUpdate
指定したKeyがDictionaryにあれば、指定したデリゲートでValueを更新します、ないなら指定したValueと共にKeyを追加します。
ConcurrentDictionaryの同名のメソッドをまねたものです。
public static TValue AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory)
=> dict[key] = dict.TryGetValue(key, out TValue result) ? updateValueFactory(key, result) : addValue;
public static TValue AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue addValue, Func<TValue, TValue> updateValueFactory)
=> dict[key] = dict.TryGetValue(key, out TValue result) ? updateValueFactory(result) : addValue;
public static TValue AddDefaultOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue, TValue> updateValueFactory)
=> dict.AddOrUpdate(key, default(TValue), updateValueFactory);
public static TValue AddDefaultOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TValue, TValue> updateValueFactory)
=> dict.AddOrUpdate(key, default(TValue), updateValueFactory);
public static TValue AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
=> dict[key] = dict.TryGetValue(key, out TValue result) ? updateValueFactory(key, result) : addValueFactory(key);
public static TValue AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TValue> addValueFactory, Func<TValue, TValue> updateValueFactory)
=> dict[key] = dict.TryGetValue(key, out TValue result) ? updateValueFactory(result) : addValueFactory();
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.AddOrUpdate)}");
Console.WriteLine($"A result = {sourceDict.AddOrUpdate("A", 1, (_, oldValue) => oldValue * 10)}");
Console.WriteLine($"B result = {sourceDict.AddOrUpdate("B", 2, (_, oldValue) => oldValue * 10)}");
Console.WriteLine($"B result = {sourceDict.AddOrUpdate("B", 2, (oldValue) => oldValue * 10)}");
Console.WriteLine($"C result = {sourceDict.AddOrUpdate("C", 3, (oldValue) => oldValue * 10)}");
Console.WriteLine($"D result = {sourceDict.AddDefaultOrUpdate("D", (oldValue) => oldValue + 40)}");
Console.WriteLine($"D result = {sourceDict.AddDefaultOrUpdate("D", (oldValue) => oldValue + 40)}");
Console.WriteLine($"E result = {sourceDict.AddOrUpdate("E", _ => 5, (_, oldValue) => oldValue * 10)}");
Console.WriteLine($"E result = {sourceDict.AddOrUpdate("E", () => 5, (oldValue) => oldValue * 10)}");
ConsoleWriteDictionary(() => sourceDict);
# AddOrUpdate
A result = 100
B result = 200
B result = 2000
C result = 3
D result = 0
D result = 40
E result = 5
E result = 50
sourceDict: { A = 100, B = 2000, C = 3, D = 40, E = 50, }
AddRange
複数のKeyValuePairを追加します。
DicitionaryをLINQで加工した後などをまるごと追加したい場合に使用します。
public static void AddRange<TKey, TValue>(this IDictionary<TKey, TValue> source, IEnumerable<KeyValuePair<TKey, TValue>> addPairs)
{
foreach (var kv in addPairs)
{
source.Add(kv);
}
}
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.AddRange)}");
var secondDict = new Dictionary<string, int>
{
["C"] = 300,
["D"] = 400,
};
sourceDict.AddRange(secondDict);
ConsoleWriteDictionary(() => sourceDict);
# AddRange
sourceDict: { A = 10, B = 20, C = 300, D = 400, }
RemoveByValue
DictionaryのRemoveメソッドはKeyを指定して要素を削除します。
このメソッドはValueを指定して一致する要素をすべて削除します。
public static void RemoveByValue<TKey, TValue>(this IDictionary<TKey, TValue> source, TValue value)
{
var removeKeys = source
.Where(x => EqualityComparer<TValue>.Default.Equals(x.Value, value))
.Select(x => x.Key)
.ToArray();
foreach (var key in removeKeys)
{
source.Remove(key);
}
}
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.RemoveByValue)}");
sourceDict.RemoveByValue(10);
ConsoleWriteDictionary(() => sourceDict);
# RemoveByValue
sourceDict: { B = 20, }
TryRemove
ConcurrentDictionaryにある同名のメソッドをまねたものです。
指定したKeyを削除しようと試みます。削除に成功した場合はout 引数に削除前の値が入ります。
public static bool TryRemove<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, out TValue value)
{
source.TryGetValue(key, out value);
return source.Remove(key);
}
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.TryRemove)}");
var successRemoveA = sourceDict.TryRemove("A", out int valueA);
var successRemoveC = sourceDict.TryRemove("C", out int valueC);
Console.WriteLine($"Remove A {successRemoveA} value = {valueA}");
Console.WriteLine($"Remove C {successRemoveC} value = {valueC}");
ConsoleWriteDictionary(() => sourceDict);
# TryRemove
Remove A True value = 10
Remove C False value = 0
sourceDict: { B = 20, }
ReplaceKey
Valueはそのままで、Keyだけ差し替えます。
public static void ReplaceKey<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey previousKey, TKey newKey)
{
var value = source[previousKey];
source.Remove(previousKey);
source.Add(newKey, value);
}
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.ReplaceKey)}");
sourceDict.ReplaceKey("A", "AAA");
ConsoleWriteDictionary(() => sourceDict);
# ReplaceKey
sourceDict: { AAA = 10, B = 20, }
KeyValuePair変換系
KeyValuePair生成
keyとValueを指定してKeyValuePairを生成する方法と
TupleとValueTupleから変換するメソッドです。
直接 new KeyValuePair<...>(...
とするよりも型名を書く量が減ります。
public static KeyValuePair<TKey, TValue> CreateKeyValuePair<TKey, TValue>(TKey key, TValue value)
=> new KeyValuePair<TKey, TValue>(key, value);
public static KeyValuePair<TKey, TValue> ToKeyValuePair<TKey, TValue>(this Tuple<TKey, TValue> tuple)
=> CreateKeyValuePair(tuple.Item1, tuple.Item2);
public static KeyValuePair<TKey, TValue> ToKeyValuePair<TKey, TValue>(this (TKey key, TValue value) tuple)
=> CreateKeyValuePair(tuple.key, tuple.value);
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.CreateKeyValuePair)}, Tuple {nameof(DictionaryExtension.ToKeyValuePair)}");
sourceDict.AddRange(new KeyValuePair<string, int>[]
{
DictionaryExtension.CreateKeyValuePair("C", 300),
("D", 400).ToKeyValuePair(),
Tuple.Create("E",500).ToKeyValuePair(),
});
ConsoleWriteDictionary(() => sourceDict);
# CreateKeyValuePair, Tuple ToKeyValuePair
sourceDict: { A = 10, B = 20, C = 300, D = 400, E = 500, }
ToTuple
こんどは逆方向のKeyValuePairからTupleとValueTupleに変換します。
public static Tuple<TKey, TValue> ToTuple<TKey, TValue>(this KeyValuePair<TKey, TValue> kv)
=> new Tuple<TKey, TValue>(kv.Key, kv.Value);
public static (TKey key, TValue value) ToValueTuple<TKey, TValue>(this KeyValuePair<TKey, TValue> kv)
=> (kv.Key, kv.Value);
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# KeyValuePair {nameof(DictionaryExtension.ToTuple)}");
Console.WriteLine($"Tuple = {sourceDict.First().ToTuple()}");
Console.WriteLine($"ValueTuple = {sourceDict.First().ToValueTuple()}");
# KeyValuePair ToTuple
Tuple = (A, 10)
ValueTuple = (A, 10)
Dictionary変換系
ToDictionary
Dictionaryを複製(ディープコピー)したい時やLINQで加工した後に再度Dictionaryにしたい時に使います。
public static Dictionary<Tkey, TValue> ToDictionary<Tkey, TValue>(this IEnumerable<KeyValuePair<Tkey, TValue>> source)
=> source.ToDictionary(
keySelector: kv => kv.Key,
elementSelector: kv => kv.Value);
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# ToDictionary (Copy)");
//Dictionaryを別のDictionaryに変換(値コピー)
var copiedDict = sourceDict.ToDictionary();
//元のDictionaryを変更しても、コピー先には反映されない
copiedDict["A"] = 100;
//LINQで加工したものもDictionaryにできる
var filterdDict = sourceDict.Where(x => x.Key == "A").ToDictionary();
Console.WriteLine("(after copy and change )");
ConsoleWriteDictionary(() => copiedDict);
ConsoleWriteDictionary(() => sourceDict);
ConsoleWriteDictionary(() => filterdDict);
# ToDictionary (Copy)
(after copy and change )
copiedDict: { A = 100, B = 20, }
sourceDict: { A = 10, B = 20, }
filterdDict: { A = 10, }
SelectValue
Keyはそのままで、ValueのみSelectで加工したい時に使用します。
通常のSelectと同じでIndex付きのオーバーロードもあります。
最後に前述のToDictionary()メソッドを使うことで直接Dictionaryに戻せます。
public static IEnumerable<KeyValuePair<Tkey, TValueResult>> SelectValue<Tkey, TValueSource, TValueResult>(this IEnumerable<KeyValuePair<Tkey, TValueSource>> source, Func<TValueSource, TValueResult> valueSelector)
=> source.Select(kv => new KeyValuePair<Tkey, TValueResult>(kv.Key, valueSelector(kv.Value)));
public static IEnumerable<KeyValuePair<Tkey, TValueResult>> SelectValue<Tkey, TValueSource, TValueResult>(this IEnumerable<KeyValuePair<Tkey, TValueSource>> source, Func<TValueSource, int, TValueResult> valueSelector)
=> source.Select((kv, index) => new KeyValuePair<Tkey, TValueResult>(kv.Key, valueSelector(kv.Value, index)));
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.SelectValue)}");
var valueChangedDict = sourceDict.SelectValue((x, i) => x * 0.01 + i).ToDictionary();
ConsoleWriteDictionary(() => valueChangedDict);
# SelectValue
convertedDict: { A = 0.1, B = 1.2, }
SelectKey
SelectValueのKey版です。
public static IEnumerable<KeyValuePair<TKeyResult, TValue>> SelectKey<TKeySource, TValue, TKeyResult>(
this IEnumerable<KeyValuePair<TKeySource, TValue>> source,
Func<TKeySource, TKeyResult> keySelector)
=> source.Select(kv => new KeyValuePair<TKeyResult, TValue>(keySelector(kv.Key), kv.Value));
public static IEnumerable<KeyValuePair<TKeyResult, TValue>> SelectKey<TKeySource, TValue, TKeyResult>(
this IEnumerable<KeyValuePair<TKeySource, TValue>> source,
Func<TKeySource, int, TKeyResult> keySelector)
=> source.Select((kv, index) => new KeyValuePair<TKeyResult, TValue>(keySelector(kv.Key, index), kv.Value));
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.SelectKey)}");
var keyChangedDict = sourceDict.SelectKey((x, i) => $"{x}-{i}").ToDictionary();
ConsoleWriteDictionary(() => keyChangedDict);
# SelectKey
keyChangedDict: { A-0 = 10, B-1 = 20, }
FlipKeyValue
KeyとValueを入れ替えたDictionaryを返します。
デモにもあるように、Valueが重複していると例外が発生します。
public static Dictionary<TValueToKey, TkeyToValue> FlipKeyValue<TkeyToValue, TValueToKey>(this IEnumerable<KeyValuePair<TkeyToValue, TValueToKey>> source)
=> source.ToDictionary(
keySelector: kv => kv.Value,
elementSelector: kv => kv.Key);
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.FlipKeyValue)}");
var flipedDict = sourceDict.FlipKeyValue();
ConsoleWriteDictionary(() => flipedDict);
//実行時エラー 複数のKeyに同じValueがあった場合、入れ替えるとKeyが重複してしまう
//sourceDict["B"] = 10;
//flipedDict = sourceDict.FlipKeyValue();
# FlipKeyValue
flipedDict: { 10 = A, 20 = B, }
DistinctByKey
LINQで加工した後に、Keyが重複した時などはそのままDictionaryにするとKeyの重複で例外が発生してしまうので、
このDistinctByKeyで重複を除きます。
Keyが重複していたら、先にあるValueが使用されます。
内部でKeyの一致を判定するIEqualityComparer継承クラスを定義して使用しています。
public static IEnumerable<KeyValuePair<TKey, TValue>> DistinctByKey<TKey, TValue>(
this IEnumerable<KeyValuePair<TKey, TValue>> source)
=> source.Distinct(OnlyKeyComparer<TKey, TValue>.Instance);
public class OnlyKeyComparer<Tkey, TValue> : IEqualityComparer<KeyValuePair<Tkey, TValue>>
{
public static OnlyKeyComparer<Tkey, TValue> Instance { get; } = new OnlyKeyComparer<Tkey, TValue>();
public bool Equals(KeyValuePair<Tkey, TValue> x, KeyValuePair<Tkey, TValue> y)
=> EqualityComparer<Tkey>.Default.Equals(x.Key, y.Key);
public int GetHashCode(KeyValuePair<Tkey, TValue> obj) => obj.Key.GetHashCode();
}
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.DistinctByKey)}");
var distinctDict = sourceDict.SelectKey(_ => "X").DistinctByKey().ToDictionary();
ConsoleWriteDictionary(() => distinctDict);
# DistinctByKey
distinctDict: { X = 10, }
Merge
2つのDictionaryを合流させた1つのDictionaryを返します。
オーバーロード2つのうち、引数が少ない方はKeyが重複していたら、先のDictionaryのValueが使用されます。
もう1つは重複した場合の挙動を細かく制御するためのデリゲートを受け取ります。
public static IEnumerable<KeyValuePair<TKey, TValue>> Merge<TKey, TValue>(
this IEnumerable<KeyValuePair<TKey, TValue>> firstDict,
IEnumerable<KeyValuePair<TKey, TValue>> secondDict)
=> firstDict.Concat(secondDict).DistinctByKey();
public static IEnumerable<KeyValuePair<TKey, TValue>> Merge<TKey, TValue>(
this IEnumerable<KeyValuePair<TKey, TValue>> firstDict,
IEnumerable<KeyValuePair<TKey, TValue>> secondDict,
Func<TKey, TValue, TValue, TValue> mergerIfDoubled)
=> firstDict.Concat(secondDict)
.ToLookup(kv => kv.Key)
.Select(kVs => kVs.Count() <= 1 ? kVs.First() :
new KeyValuePair<TKey, TValue>(
kVs.Key,
mergerIfDoubled(kVs.Key, kVs.First().Value, kVs.Last().Value)));
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.Merge)}");
var secondDict = new Dictionary<string, int>
{
["D"] = 400,
["B"] = 200,
};
ConsoleWriteDictionary(() => secondDict);
//2つのDictionaryを合流、Keyが重複していたら、先のDictionaryのValueが使用される
var mergedDict = sourceDict.Merge(secondDict).ToDictionary();
var mergedPlusedDict = sourceDict.Merge(secondDict, (k, f, s) => k.Length + f + s).ToDictionary();
ConsoleWriteDictionary(() => mergedDict);
ConsoleWriteDictionary(() => mergedPlusedDict);
# Merge
secondDict: { D = 400, B = 200, }
mergedDict: { A = 10, B = 20, D = 400, }
mergedPlusedDict: { A = 10, B = 221, D = 400, }
JoinByKey
LINQの中でも強力そうだけど、シグネチャがいかつくて、とっつきづらいJoin
のKeySelctorをDictionaryのKeyに固定したものです。
Mergeは基本的に重複がない2つの同型のDictionary(KeyValuePairコレクション)を1つに加工するものです。
JoinByKeyは2つのDictionaryからKeyが共通な要素を抜き出し、1つに加工するものです。
Mergeと違い2つの入力と出力のValueの型は同じである必要がありません。
public static IEnumerable<KeyValuePair<TKey, TValueResult>> JoinByKey<TKey, TValueFirst, TValueSecond, TValueResult>(
this IEnumerable<KeyValuePair<TKey, TValueFirst>> firstDict,
IEnumerable<KeyValuePair<TKey, TValueSecond>> secondDict,
Func<TKey, TValueFirst, TValueSecond, TValueResult> resultSelector)
=> firstDict.Join(secondDict,
fKV => fKV.Key,
sKV => sKV.Key,
(f, s) => new KeyValuePair<TKey, TValueResult>(f.Key, resultSelector(f.Key, f.Value, s.Value)));
sourceDict = CreateSourceDictionary();
Console.WriteLine($"# {nameof(DictionaryExtension.JoinByKey)}");
var secondDict = new Dictionary<string, bool>
{
["B"] = true,
["D"] = false,
};
ConsoleWriteDictionary(() => secondDict);
var joinedDict = sourceDict.JoinByKey(secondDict, (k, f, s) => k.Length + f + (s ? 100 : -100)).ToDictionary();
ConsoleWriteDictionary(() => joinedDict);
# JoinByKey
secondDict: { B = True, D = False, }
joinedDict: { B = 121, }
注意点
多くのメソッドで汎用性を持たせるためにIDictionary<TKey, TValue>
を使用していますが、パフォーマンス面ではDictionary<TKey, TValue>
を使用したほうが良いです。
参考:http://www.nimaara.com/2016/03/06/beware-of-the-idictionary-tkey-tvalue/
あとがき
こんなに長くなる予定じゃなかった。
こんなのもあるかも、これがあるならあれも、とかやってたらこんな量に。
環境
VisualStudio2017
.NET Framework 4.7
C#7