👥

【C#】GroupByのキーに型を付けたい

2023/05/25に公開

概要

C#のGroupByでキー指定をする際、record型を使用すると便利だよという話です。
※record型はC#9からの機能です。

よくある例

GroupByの複数キーを指定する場合、よく出てくる例が匿名型を使用する例です。
new { x.支店, x.役職 }の部分で匿名型を使用しています。

// 社員リストを支店と役職でグループ化する
var groupList = 社員リスト.GroupBy(
    x => new { x.支店, x.役職 });

recordを使用した例

record型を使用して複数キーを指定する例です。
new 社員グループキー(x.支店, x.役職)の部分でrecord型を使用しています。

// 社員リストを支店と役職でグループ化する
var groupList = 社員リスト.GroupBy(
    x => new 社員グループキー(x.支店, x.役職));

record 社員グループキー(支店 支店, string 役職);

匿名型だと何が不便か

匿名型を使用するとシンプルに書けて便利ですが、型情報を別メソッドに伝えることができません。
例えば次のコードのようにキーを別メソッドに渡して処理したい場合、パラメータがdynamic型となるため、メソッド内で型チェックが効きません。

// dynamicで渡すと型チェックされず実行時エラーになる
// does not contain a definition for '存在しないプロパティ'が発生
var key処理 = (dynamic key)
    => Console.WriteLine($"{key.存在しないプロパティ}");

// 社員リストを支店と役職でグループ化する
var groupList = 社員リスト.GroupBy(
    x => new { x.支店, x.役職 });

foreach (var grp in groupList)
{
    // キーに対して処理をしたい
    key処理(grp.Key);
}

一方でrecord型を使用するとパラメータに型指定できるため、コンパイルエラーとなってくれます。型安全に開発ができるのでおすすめです。

// 型チェックされるため、ここでコンパイルエラーとなる
var key処理 = (社員グループキー key)
    => Console.WriteLine($"{key.存在しないプロパティ}");

// 社員リストを支店と役職でグループ化する
var groupList = 社員リスト.GroupBy(
    x => new 社員グループキー(x.支店, x.役職));

foreach (var grp in groupList)
{
    // キーに対して処理をしたい
    key処理(grp.Key);
}

record 社員グループキー(支店 支店, string 役職);

まとめ

GroupByキーにrecordを使用する例をあまり見なかったので、情報共有でした。
コードも短く、型安全になるので参考にしてみてください。
以下今回使用した全コードを掲載しておきます。

var 社員リスト = new List<社員>
{
    new 社員(支店.東京, "課長", "一郎"),
    new 社員(支店.東京, "社員", "次郎"),
    new 社員(支店.東京, "課長", "三郎"),
    new 社員(支店.大阪, "社員", "四郎"),
    new 社員(支店.大阪, "課長", "五郎"),
    new 社員(支店.大阪, "社員", "六郎"),
};

var item処理 = (社員 item)
    => Console.WriteLine(item);

var key処理 = (社員グループキー key)
    => Console.WriteLine(key);

// 社員リストを支店と役職でグループ化する
var groupList = 社員リスト.GroupBy(
    x => new 社員グループキー(x.支店, x.役職));

foreach (var grp in groupList)
{
    // キーに対して処理をしたい
    key処理(grp.Key);

    foreach (var item in grp)
    {
        // itemに対して処理をしたい
        item処理(item);
    }
}

record 社員グループキー(支店 支店, string 役職);
record 社員(支店 支店, string 役職, string 社員名);
enum 支店 { 東京, 大阪, }

実行結果

社員グループキー { 支店 = 東京, 役職 = 課長 }
社員 { 支店 = 東京, 役職 = 課長, 社員名 = 一郎 }
社員 { 支店 = 東京, 役職 = 課長, 社員名 = 三郎 }
社員グループキー { 支店 = 東京, 役職 = 社員 }
社員 { 支店 = 東京, 役職 = 社員, 社員名 = 次郎 }
社員グループキー { 支店 = 大阪, 役職 = 社員 }
社員 { 支店 = 大阪, 役職 = 社員, 社員名 = 四郎 }
社員 { 支店 = 大阪, 役職 = 社員, 社員名 = 六郎 }
社員グループキー { 支店 = 大阪, 役職 = 課長 }
社員 { 支店 = 大阪, 役職 = 課長, 社員名 = 五郎 }

Discussion