40
LINQ にににに

C#を始めたばかりの人へのLINQ to Objects

Embed Size (px)

Citation preview

LINQについて

コードの品質について プログラミングをしてる時に遭遇する様々な問題

変数名が適当だと何のための変数なのかわかりにくい ブロックのネストが深くなると読みにくい ローカル変数が多いと何がどうなっているか混乱しやすい 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 を使って書きなおしてみましょう

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 とは Language Integrated Query

日本語にすると “言語に統合されたクエリ”

LINQ の種類 LINQ にはいくつも種類があり、代表的なもので

LINQ to Objects LINQ to SQL LINQ to Entities LINQ to XMLなど

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 の代表的なメソッド ポイント

基本的に次のメソッドには 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 日以内に作成されたものを取得してくる↑

LINQ まとめ

コードの品質を高め、生産性を上げることができます。

LINQ が使える場所では絶対に使いましょう。

おわり