Your SlideShare is downloading. ×
LINQでの活用例
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Introducing the official SlideShare app

Stunning, full-screen experience for iPhone and Android

Text the download link to your phone

Standard text messaging rates apply

LINQでの活用例

299
views

Published on

2015/02/20(金)の勉強会の資料です。

2015/02/20(金)の勉強会の資料です。

Published in: Technology

0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
299
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
2
Comments
0
Likes
5
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. UnityでのLINQ活用例 室星亮太 2015/02/20(金)
  • 2. 質問です
  • 3. Unityでゲームを作成している方?
  • 4. LINQ知っている方?
  • 5. LINQ使っている方?
  • 6. LINQがっつり使っている方?
  • 7. ありがとうございました
  • 8. UnityでのLINQの活用事例を通し、 LINQの良さや利用方法を紹介します。
  • 9. 今日の目標 みなさんに • LINQを使うとコードが読みやすくなることを知ってもらう • LINQを活用できるシーンが多いことを知ってもらう • LINQを使ってみよう・勉強してみようと思ってもらう • LINQにこんな便利なメソッドあったんだと知ってもらう
  • 10. Unityでの例を通して!
  • 11. ところで
  • 12. ゲーム開発で大切なもの たくさんありますよね
  • 13. その中のひとつ
  • 14. 簡潔で読みやすいコード
  • 15. なぜかって? 動けば同じじゃないかって?
  • 16. 仕様、変わりますよね? リリース後もアプデしますよね?
  • 17. 「汚いコードでとりあえず動く物」 短期的には早いかもしれない ×"短期的な開発速度 ⃝!中・長期的な開発速度
  • 18. 仕様は開発途中で変わりますよね? 簡潔で読みやすく奇麗なコード これに比べて汚く長いコードの 仕様変更はとても大変!
  • 19. 遊びながら試行錯誤し面白い物を作る それには簡潔で読み安いコードが必要! あと汚く長いコード、まじアプデしんどい!
  • 20. 読みやすいコードは大切 賛成していただけますか?
  • 21. ところで今日のテーマは LINQです
  • 22. LINQを使うと、 リストなどのコレクションを扱うコードが、 簡潔に読みやすくなりますよ!
  • 23. さて、LINQとは
  • 24. LINQとは 統合言語クエリ!(LINQ)!は、強力なクエリ機能を!C#!言語および! Visual!Basic!言語の構文へと拡張する、Visual!Studio!2008!で導入 された機能のセットです。! MSDN%LINQよりh*ps://msdn.microso7.com/ja:jp/library/bb397926.aspx
  • 25. イメージわきます?
  • 26. 配列とかリストとかディクショナリとか シーケンス(↑のようなオブジェの集まり)を を扱うコードがLINQを使うと綺麗に書ける ってことだけとりあえず今はイメージしてください(適当ですいません) 今日お話しするのは、LINQ%to%Objectsです 今日の話で「LINQが使えるようになる」とはなりません。みなさんに便利さを紹介しようと思います。
  • 27. 自己紹介 • 名前:室星亮太 • 所属:Fuller,)Inc. • 仕事:Unityを用いたゲーム開発 • 投稿先:h3p://qiita.com/RyotaMurohoshi • Twi3er:@RyotaMurohoshi • コミュニティ:Androidの会Unity部
  • 28. ライブラリUniLinq作ったり UniBookでLINQについて書いたり
  • 29. LINQ大好きです! 「LINQのないC#なんてC#じゃない」 って言う方もいますが、私もそう思います!
  • 30. そんな私が こういう風に使える、こんな状況で使える という例をガンガン紹介していきます
  • 31. 第一部 今すぐに使って欲しいメソッド3選 Count・All・Any
  • 32. こんなクラスがあって public class Monster { public int Id { get; set; } public string Name { get; set; } public int Hp { get; set; } /* 中略 */ }
  • 33. List<Monster>の中でHpが0以下の要素を数える のようなリスト内の指定の条件を満たした要素を数える ってことやりません?
  • 34. Count 特定の条件を満たした要素を数える
  • 35. Hpが0以下の要素、つまり死んでいる モンスター(Monster)の数を数えたい
  • 36. よくあるforeach、ifのコード List<Monster> monsterList = LoadMonsterList (); // LINQを使わないと int deadMonsterCount = 0; foreach (Monster monster in monsterList) { if(monster.Hp <= 0) { deadMonsterCount++; } }
  • 37. Count 指定の条件を満たした要素を数える List<Monster> monsterList = LoadMonsterList (); // Hpが0以下の要素の数に int deadMonsterCount = monsterList.Count ((Monster monster) => monster.Hp <= 0);
  • 38. さっきの2つのコード どう読めます?
  • 39. このコード、どう読みます? List<Monster> monsterList = LoadMonsterList (); // LINQを使わないと int deadMonsterCount = 0; foreach (Monster monster in monsterList) { if(monster.Hp <= 0) { deadMonsterCount++; } }
  • 40. int型のdeadMonsterCountを0で初期化 foreach文でmonsterListをまわす もし要素のmonsterのHpが0以下ならば deadMonsterCountをインクリメント
  • 41. あ、つまり! HPが0以下のモンスターを数えるのか! という1回頭の中での変換が必要
  • 42. 一方LINQは?どう読みます? // LINQを使うと List<Monster> monsterList = LoadMonsterList (); // Hpが0以下の要素の数に int deadMonsterCount = monsterList.Count ((Monster monster) => monster.Hp <= 0);
  • 43. int型のdeadMonsterCountを次の値で初期化 monsterList内の次の条件を満たす要素数だ! 条件は要素のHPが0以下だ!
  • 44. ほぼ意味的に、 「Hpが0以下のモンスターを数えろ!」 左から右に素直に読める!
  • 45. foreach文、if文のは 「どう処理しているか」が書かれている LINQでは 「何がしたいか」が書かれている
  • 46. だからLINQのコードは読みやすい しかも簡潔
  • 47. ここからは基本、LINQ版のみでさくさく行きます
  • 48. Any 特定の条件を満たす要素が 存在するか調べる
  • 49. Any 特定の条件を満たす要素が存在するか調べる List<Monster> monsterList = LoadMonsterList (); // Hpが0以下のモンスターが存在すればtrue bool isExistDeadMonster = monsterList.Any((Monster monster) => monster.Hp <= 0);
  • 50. All 全ての要素が指定の条件を 満たすか調べる
  • 51. All 全ての要素が指定の条件を満たすか調べる List<Monster> monsterList = LoadMonsterList (); // 全てのモンスターのHpが0以下ならばtrue bool isAllMonsterDead = monsterList.All((Monster monster) => monster.Hp <= 0);
  • 52. 次はUnityっぽいので かつ 割とマイナーなやつを
  • 53. 第二部 Editor拡張でも活躍 OfType
  • 54. OfType 指定の型の要素のみにフィルタリングし、 指定の型のシーケンスに変換
  • 55. Projectウィンドウ中で選択中の物の中で、 テクスチャ(Textureクラス)のみを処理する (UnityEditor拡張)
  • 56. • UnityEditor.Selec0onクラスのobjectsプロパティを利用 • Selec0on.objectsは現在選択しているものを取得 • 型はUnityEngine.Object[]で取得 • これには当然テクスチャ(Textureクラス)以外のものも含まれる • けどTextureのみを対象にしたい
  • 57. フィルタリングといえばWhere これをis演算子とあわせて foreach (UnityEngine.Object target in Selection.objects.Where (o => o is Texture)){ // キャストする or as演算子利用がめんどい Texture texture = target as Texture; // ここでTextureに何かしらの処理 }
  • 58. Castメソッドでキャストもできますが foreach (Texture texture in Selection.objects .Where (o => o is Texture) .Cast<Texture>()){ // ここでTextureに何かしらの処理 }
  • 59. そこでOfTypeですよ!
  • 60. OfType 指定の型の要素のみにフィルタリングし、指定の型のシーケンスに変換 // Selection.objectsの中の要素をTexture型のみにフィルタリングし、 // IEnumerable<Texture>に変換 foreach (Texture texture in Selection.objects.OfType<Texture> ()){ // ここでTextureに何かしらの処理 }
  • 61. UnityのEditor拡張で、Selec.onは利用シーンが多い! (MenuItemと合わせて導入が楽!) 「Selec%on.objects.OfType<Texture>7()」、ぜひ! 実際に使った例がこちら!!h#p://qiita.com/RyotaMurohoshi/items/b01e3cdb91fea96f4574
  • 62. OfTypeメソッド 他ではNUnitLiteというライブラリを使った際 System.Objectから特定の型への変換でも使いました
  • 63. Editor拡張でのLINQ活用 LINQ、実はiOSで問題があります けれどUnityEditor内であれば大丈夫! Editor拡張から利用し始めるのはオススメ!
  • 64. 第三部 指定の条件を満たす先頭の要素を取得する First、FirstOrDefault
  • 65. List<Monster>の中でHpが0以下の要素が一つ欲しい のようなリスト内の指定の条件を満たす要素を一つ取得する ってことやりません?
  • 66. First 指定の条件を満たす先頭の要素を取得する 条件を満たした要素がない場合は例外発生
  • 67. First 指定の条件を満たす先頭の要素を取得する。条件を満たした要素がない場合は例外発生 List<Monster> monsterList = LoadMonsterList (); // ひとつもHpが0以下の要素が存在しないなら例外発生 Monster deadMonster = monsterList .First ((Monster monster) => monster.Hp <= 0);
  • 68. Firstメソッドは 条件を満たす要素がない場合例外が発生! じゃあ例外を発生たくない場合? Anyで存在チェックしてあった場合のみ、もしくは...
  • 69. FirstOrDefault 指定の条件を満たす先頭の要素を取得する 条件を満たした要素がない場合は その型の規定値を返す
  • 70. 条件を満たした要素がない場合、 「規定値を返す=nullを返す」じゃない! ここ要注意!値型の規定値はnullじゃない!
  • 71. 規定値 • クラス型の場合、null • intの場合、0 • floatの場合、0.0F • boolの場合、false • Vector3の場合、x、y、zが全て0.0F
  • 72. FirstOrDefault 指定の条件を満たす先頭の要素を取得する。条件を満たした要素がない場合はその型の規定値を返す List<Monster> monsterList = LoadMonsterList (); // ひとつもHpが0以下の要素が存在しないならMonsterクラスの規定値nullに Monster deadMonster = monsterList .FirstOrDefault ((Monster monster) => monster.Hp <= 0);
  • 73. ここでさらにもう一つ!
  • 74. DefaultIfEmpty シーケンスが空ならば引数で渡したものを 唯一の要素とするシーケンスに変換
  • 75. Firstと組み合わせてこんなことができる
  • 76. DefaultIfEmpty シーケンスが空ならば引数で渡したものを唯一の要素とするシーケンスに変換 List<Monster> monsterList = LoadMonsterList (); Monster defaultMonster = GetDefaultMonster (); int targetId = GetTargetId (); // 指定IDのモンスターを探す。なかったらdefaultMonsterに。 Monster targetMonster = monsterList .Where ((Monster monster) => monster.Id == targetId) .DefaultIfEmpty (defaultMonster) .First (); // このオーバーロードは単純に先頭要素取得
  • 77. ところで
  • 78. デリゲートとラムダ式 • 「(Monster*monster)*=>*monster.Hp*<=*0」はラムダ式 • 「ラムダ式」ではなく「デリゲート」を引数にとる • LINQの多くのメソッドがFunc<T,*TResult>を取る • ラムダ式の用途の一つはデリゲートの生成 詳しくは! h"p://qiita.com/RyotaMurohoshi/items/740151bd772889cf07de にまとめました
  • 79. 第四部 算術計算系 Max、Min、Sum、Average
  • 80. こんなクラスを使います public class PlayLog { public int StageId { get; set; } public int Score { get; set; } public DateTime PlayedAt { get; set; } public TimeSpan PlayTime { get; set; } }
  • 81. List<PlayLog>の要素の中で一番大きいScoreを求める のようなリスト内要素を変換して、その一番大きい値を求める ってことやりません?
  • 82. Max 各要素に変換を施しその最大値を計算する
  • 83. Max 各要素に変換を施しその最大値を計算する List<PlayLog> playLogList = LoadPlayLogList (); // PlayLogListのScoreの最大値 int maxScore = playLogList.Max ((PlayLog playLog) => playLog.Score);
  • 84. Min 各要素に変換を施しその最小値を計算する
  • 85. Min 各要素に変換を施しその最小値を計算する List<PlayLog> playLogList = LoadPlayLogList (); // PlayLogListのScoreの最小値 int minScore = playLogList.Min ((PlayLog playLog) => playLog.Score);
  • 86. Sum 各要素に変換を施しその合計値を計算する
  • 87. Sum 各要素に変換を施しその合計値を計算する List<PlayLog> playLogList = LoadPlayLogList (); // PlayLogListのScoreの合計値 int sumOfScore = playLogList.Sum ((PlayLog playLog) => playLog.Score);
  • 88. Average 各要素に変換を施しその平均値を計算する
  • 89. Average 各要素に変換を施しその平均値を計算する List<PlayLog> playLogList = LoadPlayLogList (); // PlayLogListのScoreの平均値 double averageOfScore = playLogList.Average ((PlayLog playLog) => playLog.Score);
  • 90. ところで
  • 91. LINQとiOS(1) • Unity+iOSだと実機実行時エラーになることがある • 理由はAOTコンパイルがうまくいっていないみたい • それを解決するためにUniLinqっていうやつを作った
  • 92. LINQとiOS(2) • IL2CPP登場 • IL2CPPで大丈夫か試したら、テストするやつが実機でエラーに • AssemblyクラスのCodePathプロパティがnullなのが原因 • 今後も正しく動くかどうかなど、継続して調べていきます!
  • 93. 第五部 並び替え OrderBy、OrderByDescending ThenBy、ThenByDescending
  • 94. List<PlayLog>をScroreの降順で並び変える のような整列操作 ってやりません?
  • 95. OrderBy 各要素に変換を施し その結果で並び替え
  • 96. OrderBy 各要素に変換を施しその結果で並び替え List<PlayLog> playLogList = LoadPlayLogList (); // スコアの降順で並び替え IEnumerable<PlayLog> orderedPlayLog = playLogList .OrderByDescending ((PlayLog playLog) => playLog.Score); OrderByは昇順で並び替え、OrderByDescendingは降順で並び替え 元のデータを並び変えるのでなく、新たに並び替えたシーケンスをつくる
  • 97. もし同じScoreならどうしよう? 同ScoreならばPlayTimeが短い順にしたい場合は?
  • 98. もう一度OrderByを呼ぶのは間違い!
  • 99. ThenBy OrderByで並び替えたもので、 同評価だったものを並び替え
  • 100. ThenBy OrderByで並び替えたもので、同評価だったものを並び替え List<PlayLog> playLogList = LoadPlayLogList (); // スコアの降順で並び替えて、同点なら時間が短い方を先に IEnumerable<PlayLog> orderedPlayLog = playLogList .OrderByDescending ((PlayLog playLog) => playLog.Score) .ThenBy ((PlayLog playLog) => playLog.PlayTime);
  • 101. 第六部 スキップと取得 Skip、Take、SkipWhile、TakeWhile
  • 102. データを画面に10個ずつ表示 インデックス0の1ページ目、0番目から9番目 インデックス1の2ページ目、10番目から19番目 インデックス2の3ページ目、20番目から29番目 ってことやりません
  • 103. Skip%+%Take
  • 104. Skip 先頭から引数で渡した個数だけ 要素を飛ばす
  • 105. Take 先頭から引数で渡した個数だけ 要素を返す
  • 106. Skip+Take データを画面に10個ずつ表示、今回は3ページ目(インデックスは2) List<PlayLog> playLogList = LoadPlayLogList (); int numPerPage = 10; int pageIndex = 2; int skipNum = numPerPage * pageIndex; // 3ページ目(インデックスは2)、20番から10個の要素を取得 IEnumerable<PlayLog> selectedPlayLogs = playLogList .Skip (skipNum) .Take (numPerPage);
  • 107. 今、DateTime型のPlayedAtの昇順でPlayLogが並んでいる ある時刻から、他のある時刻までのデータを取得したい
  • 108. SkipWhile)+)TakeWhile
  • 109. SkipWhile 先頭から引数で渡した条件が真の間 要素を飛ばす
  • 110. TakeWhile 先頭から引数で渡した条件が真の間 要素を返す
  • 111. SkipWhile)+)TakeWhile List<PlayLog> playLogList = LoadPlayLogList (); DateTime start = GetStartTime (); DateTime end = GetEndTime (); // PlayedAtがstartより後or同時刻で、endより前な要素を取得 IEnumerable<PlayLog> selectedPlayLogs = playLogList .SkipWhile ((PlayLog playLog) => playLog.PlayedAt < start) .TakeWhile ((PlayLog playLog) => playLog.PlayedAt < end);
  • 112. 第七部 その他オススメ
  • 113. ElementAt シーケンス中の 指定のインデックスの要素を取得
  • 114. ⚪番インデックスの要素が欲しい こういう時、ありますよね。どうします? あまり書かないかもしれないけど、リファクタリングの途中で必要なこともある?
  • 115. 多くのLINQメソッドの返値型である、IEnumerable<T>型は インデクサで要素にアクセスできない!
  • 116. ToArrayはダメ絶対! // ToArrayで配列にしてからアクセス? // 効率悪い!!! // playLogsはIEnumerable<PlayLog>型 PlayLog index5PlayLog = playLogs.ToArray ()[5];
  • 117. ElementAt // インデクサはないけれど // ElementAtメソッドでインデックスにアクセスできる PlayLog index5PlayLog = playLogs.ElementAt (5);
  • 118. ToDic&onary シーケンスから辞書を作る キーを指定する。バリューも指定できる。
  • 119. こんなクラスを使います public class Item { public int Id { get; set; } public string Name { get; set; } public ItemType ItemType { get; set; } } public enum ItemType { Weapon, ConsumptionItem, ValuableItem }
  • 120. List<Item>*からItemのIdをキーとする Dic$onary<int,-Item>をつくる みたいなことしたくありません? それLINQで一行でかけます!
  • 121. ToDic&onary シーケンスから辞書を作る!(その1) List<Item> itemList = LoadItemList (); // Idプロパティをキーとするディクショナリの生成 // もしキーの重複があったら例外が発生 Dictionary<int, Item> itemDict = itemList .ToDictionary((Item item) => item.Id);
  • 122. ToDic&onary シーケンスから辞書を作る!(その2) List<Item> itemList = LoadItemList (); // Idプロパティをキー、Nameプロパティバリューとする // ディクショナリの生成 // もしキーの重複があったら例外が発生 Dictionary<int, string> itemDict = itemList.ToDictionary( (Item item) => item.Id, (Item item) => item.Name);
  • 123. ToLookup シーケンスからILookupを作る グループ化したコレクションを生成
  • 124. List<Item>*itemListがあって Dic$onary<ItemType,2List<Item>>が欲しい LINQにはDic(onary<ItemType,5List<Item>>に似ている ILookup<ItemType,.Item>が作れるメソッドがある
  • 125. ToLookup シーケンスからILookupを作る(グループ化したコレクションを生成) List<Item> itemList = LoadItemList (); // ILookup<ItemType, Items>は // Dictionary<ItemType,List<Item>>に似ている ILookup<ItemType, Item> itemLookup = itemList. ToLookup((Item item) => item.ItemType); // ディクショナリのように[]とキーでそのグループにアクセスできる IEnumerable<Item> weapons = itemLookup[ItemType.Weapon];
  • 126. SelectMany リストのリストの平滑化を行う
  • 127. SelectMany リストのリストの平滑化を行う(その1) // モンスターのリストのリスト List<List<Monster>> listOfMonsterList = LoadListOfMonsterList (); // モンスターのシーケンスに IEnumerable<Monster> monsters = listOfMonsterList .SelectMany ((List<Monster> list) => list);
  • 128. SelectManyはリストのリストだけが対象じゃない 結構使える!
  • 129. こんなクラスがあって public class MonsterParty { public string Name { get; set; } // Monsterのリスト、このプロパティがミソ public List<Monster> MonsterList { get; set; } }
  • 130. SelectMany リストのリストの平滑化を行う(その2) // パーティーのリスト List<MonsterParty> monsterPartyList = LoadMonsterPartyList (); // モンスターのシーケンスに IEnumerable<Monster> monsters = monsterPartyList .SelectMany ((MonsterParty party) => party.MonsterList);
  • 131. Dis$nct 重複を除く
  • 132. Dis$nct 重複を除く // シンプルな例 int[] ids = new int[]{0, 1, 1, 2, 2, 2, 3, 3, 4}; // 重複が除かれている IEnumerable<int> distinctedIds = ids.Distinct ();
  • 133. Dis$nct 重複を除く // パーティーに同じモンスターはいないが、他のパーティーには同じモンスターがいる List<MonsterParty> monsterPartyList = LoadMonsterPartyList (); // 重複の無しモンスターのIdのシーケンスを作る IEnumerable<int> monsterIds = monsterPartyList // モンスターのシーケンスにして .SelectMany (party => party.MonsterList) // Idのシーケンスにして .Select (monster => monster.Id) // 重複を排除する .Distinct ();
  • 134. ちなみにLINQには次のような集合演算を行うものもあります 差集合を求める!Except 和集合を求める!Union 積集合を求める!Intersect
  • 135. Join 二つのシーケンスをキーで突合
  • 136. PlayLogクラスとStageDataクラスから、PlayDataを作りたい public class PlayLog { // StageのIdのみでStageの情報はない public int StageId { get; set; } public int Score { get; set; } /* 中略 */ } public class StageData { public int Id { get; set; } public string Name { get; set; } /* 中略 */ }
  • 137. PlayLogクラスとStageDataクラスから、PlayDataを作りたい public class PlayData { // StageDataクラスの名前から public string StageName { get; set; } // PlayLogクラスのScoreから public int Score { get; set; } /* 中略 */ }
  • 138. List<PlayLog>なplayLogListと List<StageData>なstageDataListから PlayDataのシーケンスを作りたい
  • 139. foreachな例 // foreachな例、長い List<PlayData> playDataList = new List<PlayData> (); foreach(PlayLog playLog in playLogList) { StageData targetStageData = null; foreach(StageData stageData in stageDataList) { if (playLog.StageId == stageData.Id) { targetStageData = stageData; break; // nullなやつはないよねというコード } } playDataList.Add (new PlayData { StageName = targetStageData.Name, Score = playLog.Score, }); }
  • 140. LINQ利用な例 // LINQ利用な例、まだ長い List<PlayData> playDataList = new List<PlayData> (); foreach(PlayLog playLog in playLogList) { StageData stageData = stageDataList.First (stage => stage.Id == playLog.StageId); playDataList.Add (new PlayData { StageName = stageData.Name, Score = playLog.Score, }); }
  • 141. Joinの例 IEnumerable<PlayData> playDatas = playLogList.Join ( stageDataList, (PlayLog playLog) => playLog.StageId, // PlayLogのキー (StageData stageData) => stageData.Id, // StageDataのキー // 二つのキーが ったPlayLogとStageDataが来る (PlayLog playLog, StageData stageData) => new PlayData { StageName = stageData.Name, Score = playLog.Score, });
  • 142. さて
  • 143. LINQとのつきあい方 • エラーにならないライブラリを作るor使う • エラーになるメソッドor使い方で使わない • Editor拡張のみで使う • エラーになる問題が直るまで待つ
  • 144. 何か質問あります?
  • 145. さて、今日の目標のおさらいです
  • 146. 今日の目標 みなさんに • LINQを使うとコードが綺麗になることを知ってもらう • LINQを使える活用シーンが多いことを知ってもらう • LINQを使ってみよう・勉強してみようと思ってもらう • LINQにこんな便利なメソッドあったんだと知ってもらう
  • 147. いかがだったでしょうか? LINQ使ってみたくなりましたか?
  • 148. ありがとうございました! @RyotaMurohoshi