c#を始めたばかりの人へのlinq to objects
TRANSCRIPT
コードの品質について プログラミングをしてる時に遭遇する様々な問題
変数名が適当だと何のための変数なのかわかりにくい ブロックのネストが深くなると読みにくい ローカル変数が多いと何がどうなっているか混乱しやすい etc…
コードの品質について 何も意識せずにコードを書いているとついついネストは深く
なってしまいますよね 例として、
あるカレンダーセットの中のカレンダーの中の予定のうち、1 日以内に作成されたものを抽出する
コードをお見せします
コードがわかりにくくなる例class CalendarSet { public Calendar[] Calendars; } class Calendar { public Schedule[] Schedules; } class Schedule { public DateTime ScheduleCreatedAt; }
このようなクラス構成だとします
コードがわかりにくくなる例CalendarSet calendarSet;
var recentlyCreatedSchedules = new List<Schedule> { }; <- 結果を格納するもの
コードがわかりにくくなる例CalendarSet calendarSet;
var recentlyCreatedSchedules = new List<Schedule> { }; <- 結果を格納するものforeach (var calendar in calendarSet.Calendars) <- カレンダーセットのカレンダーでループ{
}
コードがわかりにくくなる例CalendarSet calendarSet;
var recentlyCreatedSchedules = new List<Schedule> { }; <- 結果を格納するものforeach (var calendar in calendarSet.Calendars) <- カレンダーセットのカレンダーでループ{
foreach (var schedule in calendar.Schedules) <- カレンダーの予定でループ{
} }
コードがわかりにくくなる例CalendarSet calendarSet;
var recentlyCreatedSchedules = new List<Schedule> { }; <- 結果を格納するものforeach (var calendar in calendarSet.Calendars) <- カレンダーセットのカレンダーでループ{
foreach (var schedule in calendar.Schedules) <- カレンダーの予定でループ{
if (schedule.ScheduleCreatedAt > DateTime.Now.AddDays(-1)) { ↑ 1 日以内に作成された予定を
} }
}
コードがわかりにくくなる例CalendarSet calendarSet;
var recentlyCreatedSchedules = new List<Schedule> { }; <- 結果を格納するものforeach (var calendar in calendarSet.Calendars) <- カレンダーセットのカレンダーでループ{
foreach (var schedule in calendar.Schedules) <- カレンダーの予定でループ{
if (schedule.ScheduleCreatedAt > DateTime.Now.AddDays(-1)) { ↑ 1 日以内に作成された予定を
recentlyCreatedSchedules.Add(schedule); <- 結果リストに格納}
} }
コードがわかりにくくなる例CalendarSet calendarSet;
var recentlyCreatedSchedules = new List<Schedule> { }; foreach (var calendar in calendarSet.Calendars) {
foreach (var schedule in calendar.Schedules){
if (schedule.ScheduleCreatedAt > DateTime.Now.AddDays(-1)) {
recentlyCreatedSchedules.Add(schedule); }
} }
↑ 結果を格納する一時変数が必要
ネストが深くて理解しにく
い
LINQ を使う場合
calendarSet.Calendars.SelectMany(c => c.Schedules.Select(s => s)) .Where(s => s.ScheduleCreatedAt > DateTime.Now.AddDays(-1));
LINQ を使う場合
calendarSet.Calendars.SelectMany(c => c.Schedules.Select(s => s)) .Where(s => s.ScheduleCreatedAt > DateTime.Now.AddDays(-1));
!?!?!?!?!?
LINQ を使う場合
calendarSet.Calendars.SelectMany(c => c.Schedules.Select(s => s)) .Where(s => s.ScheduleCreatedAt > DateTime.Now.AddDays(-1));
1 行で書けてしまうネストも深くならない
余計なローカル変数もないシンプルなコードで可読性も高い
LINQ to Objects
今回はオブジェクトを扱う LINQ to Objects について LINQ to Objects とは
あらゆるコレクション (IEnumerable を実装するオブジェクト)を LINQで操作可能にする
例えば 条件を満たす要素を取得 特定の値が含まれているか 操作を加える ソート 等 ができる
クエリ式とメソッド式 LINQ にはクエリ式とメソッド式の 2 つの記法がある
メソッド式 C# のメソッドと同じように” .” でチェーンして記述する方式 var q = collection.Where(x => x > 10).Select(x => x * x);
クエリ式 SQL のようなクエリ構文で LINQ を記述する方式 var q = from x in collection where x > 10 select x * x;
クエリ式とメソッド式 基本的にメソッド式を使う(一般的にも)
理由は
クエリ式は必ず from **** select ****の形式になるので、 Select() をしていないにも関わらず select が必要になり、何の処理が行われているかわかりにくい
クエリ式とメソッド式
クエリ式にはない構文がある First() Single() 等
メソッド式collection.Where(x => x > 10).Select(x => x * x).First();
クエリ式(from x in collection where x > 10 select x * x).First();
無理やり使おうとするなら、一度クエリにしてから(カッコで囲んで)呼ぶ
クエリ式とメソッド式
ただし、クエリ式のほうが優れている点もある ReactiveExtensions を使って非同期処理を実装するとき 複数の値を次のメソッドの引数として渡すとき
メソッド式でも匿名型を用いることで、できないこともない など
が今の時点では使う機会はないので存在だけ知っていればよい
LINQ の代表的なメソッド Select SelectMany Where All/Any OrderBy/OrderByDescending Contains Skip/SkipWhile Take/TakeWhile First/FirstOrDefault Concat ToArray/ToList
LINQ の代表的なメソッド Selec t
シーケンスの値を一様に変換する
var numbers = Enumerable.Range(1,10);
var number = numbers.Select(x => x * 2);
数字を 2 倍にしたものが帰ってくる
LINQ の代表的なメソッド 匿名型を使って引数を次の標準クエリ演算子へ渡す
var names = new[] { "Taro" , "Jiro" , "Saburo" , "Shiro" };
names.Select((name, index) => new { Name = name, Index = index }) .Where(o => o.Name.Length == o.Index )
.Select(o => o.Name + o.Index);
1. Name と Index を持つ匿名型のシーケンスを作成し、2. Name の文字数と Index が一致するものを選択し、3. Name と Index を結合した文字列のシーケンスが帰ってくる
LINQ の代表的なメソッド Selec t Many
シーケンスの各要素を 1 つのシーケンスに平坦化します。var items = new[] {
new[] {1,2,3,4}, new[] {10,12,14}, new[] {20,22,24}
}; var result = items.SelectMany(item => item);
平坦化されたシーケンス {1,2,3,4,10,12,14,20,22,24} が返ってくる
http://pro.art55.jp/?eid=1303957
LINQ の代表的なメソッド Where
条件に合致するものを取得する
var numbers = Enumerable.Range(1,10);
numbers.Where(x => x % 2 == 0);
偶数の数列を取り出している
LINQ の代表的なメソッド All/Any
全部の値 / 一つ以上の値 が条件を満たすかを判定するvar numbers = Enumerable.Range(1,10);
numbers.Any(x => x % 2 == 0);numbers.All(x => x > 3);
一つ目は偶数がシーケンスに含まれているかを判定 二つ目はシーケンスに含まれているすべての数字が 3 より大きいか判
定
LINQ の代表的なメソッド OrderBy/OrderByDescending
シーケンスをソートするvar names = new[] { "Taro" , "Jiro" , "Saburo" , "Shiro" };var persons = names.Select((name, index) => new { Name = name, Age = index * 2 });persons.OrderByDescending(p => p.Age);
匿名型の Age で降順でソートされた結果が返ってくる { Name = waguchi, Age = 6 } { Name = wada, Age = 4 } { Name = yamaguchi, Age = 2 } { Name = yamada, Age = 0 }
LINQ の代表的なメソッド Contains
シーケンスに特定の値が含まれているかを調べる
var names = new[] { "Taro" , "Jiro" , "Saburo" , "Shiro" };
bool b = names.Contains("hanako");
names に” hanako” が含まれているか判定
LINQ の代表的なメソッド Skip/SkipWhile
先頭から n 個を除外 / 先頭から条件を満たすものを捨てる
var names = new[] { "Taro" , "Jiro" , "" , "Saburo" , "Shiro" };
names.Skip(3);names.SkipWhile(name => name.Length <= 0);
先頭から 3 要素を除外 "Saburo" , "Shiro“
要素の長さが0以下のものを除外 "Taro" , "Jiro" , "Saburo" , "Shiro"
LINQ の代表的なメソッド Take/TakeWhile
先頭から n 個の要素を取得 / 先頭から条件を満たすものを取得
var names = new[] { "Taro" , "Jiro" , "" , "Saburo" , "Shiro" };
names.Take(3);names.TakeWhile(name => name.Length > 4);
先頭から 3 要素を取得 "Taro" , "Jiro" , ""
要素の長さが 4 より大きいのものを取得 "Saburo" , "Shiro"
LINQ の代表的なメソッド First/FirstOrDefault
シーケンスの最初の値を取得する FirstOrDefault は要素が見つからない場合は既定値を返します。var numbers = Enumerable.Range(3,10);numbers.First();
var strings = new String[] {};strings.FirstOrDefault();
それぞれ 3 nullが返ってくる
LINQ の代表的なメソッド Concat
2 つのシーケンスを連結する
var numbers = Enumerable.Range(1,5);var numbers2 = Enumerable.Range(15,20);
numbers.Concat(numbers2);
numbers と numbers2 を連結した値 { 1 , 2 , 3 , 4 , 5 , 15 , 16 , 17 , 18 , 19 , 20 } が返ってくる
LINQ の代表的なメソッド ToArray/ToList
Array/List に変換var numbers = Enumerable.Range(1,10);
numbers.ToArray();numbers.ToList();
それぞれnew[] {1,2,3,4,5,6,6,7,8,9,10};new List<int> {1,2,3,4,5,6,6,7,8,9,10};
と同じものが帰ってくる
LINQ を使うときの注意点 LINQ には List の ForEach() がない
LINQ は副作用がないので ForEach は実装されていない forEach 構文をつかうことは可能 ToList().ForEach() はシーケンスを一度 List に変換していて、処理が多くなり、メモリ消費量も多くなるため、使ってはいけない
var collection = new[] { 1 , 2, 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 };× collection.ToArray().ForEach(i => System.Console.Write(i));
○ foreach ( var i in collection ) { System.Console.WriteLine(i); }
LINQ を使うときの注意点 LINQ の Count() は使用しない
Count はクエリが実行されるたびに、シーケンスの要素を数えなおすため、必要以上に処理が走ってしまう
もし個数を数えたい場合は、一度リストにしてから数える
var collection = new[] { 1 , 2, 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 };int count = collection.ToList().Length;
LINQ を使う場合
先ほどの例に戻りますが
calendarSet.Calendars.SelectMany(c => c.Schedules.Select(s => s))
.Where(s => s.ScheduleCreatedAt > DateTime.Now.AddDays(-1));
各カレンダーの予定を平坦化されたものができる↑
そのなかから 1 日以内に作成されたものを取得してくる↑