この記事でのバージョン
Unity2018.3.9f1
はじめに
今回はLINQのかるい説明から実際に様々なメソッドを使ってみるという記事になります!
LINQと聞いて「ゼルダの伝説」を思い出す人がいるのではないでしょうか?
私は最初それしか思いつかなかったのですが、どうやら調べてみるとゼルダの伝説に登場するリンクは英語で「Link」と書くようです。
少し話が脱線してしまいましたがこのLINQはC#をある程度触ったことがある人なら聞いたことがあるかもしれません。
C#なので当然Unityでも使えるのですが、昔はスマホ(IPhone)で動かない可能性がありました。
しかし、今はどうやら大丈夫になったみたいです。
Unity+iOSでエラーになるLINQのまとめ - Qiita
心配なく使うことができるようになった訳なので、一度チャレンジしてみてはどうでしょうか。
ただIL2CPPを忘れずにしましょう!
【Unity】IL2CPPで高速化?してみた - はなちるのマイノート
LINQを使うための準備
LINQを使うにはusing ディレクティブを用いて名前空間System.Linqを指定しましょう。
using System.Linq;
なぜLINQを使うべきなのか
LINQを用いるにあたってインターフェイスの知識が多少必要です。
重要な性質はリストや配列といったIEnumerable<T>インターフェイスを実装している型ならば、どんなものであっても実行することができるということです。
実際にどれくらいのクラスがIEnumerable<T>インターフェイスを実装しているか気になると思いますが、これだけあります。
加えてLINQは「foreachのパワーアップ版」と言われることがありますが、「foreachはIEnumerable<T>インターフェイスと深い関係」にあります。
その一つとしてforeachはIEnumerableインターフェースを実装しているクラスしか処理することができません(厳密には異なります。以下の記事を参照)。
これは結構衝撃を覚えるかたもいるかもしれませんが、IEnumerableとIEnumerator,イテレーターはLINQの正しい理解をするのに大切なようです。
http://garicchi.hatenablog.jp/entry/2014/09/12/200000garicchi.hatenablog.jp
さらにもう一つ重要な性質があり、メソッドの返り値がIEnumerable<T>であるということです。
これは実際に見てもらうと分かりやすいと思います。
using System.Linq; public class Hoge : MonoBehaviour { void Start() { var list = new List<int> { 1, 2, 3, 4, 5 }; IEnumerable<string> query = list.Where(s => s <= 3) .Select(s => s.ToString()); foreach (string num in query) Debug.Log(num); //1 2 3 } }
これはメソッドチェーンと呼ばれるもので、メソッドをチェーンのようにつなげていくことができます。
またLINQを用いることでコードの量が減るだけでなく、何をやるのかが読み取りやすくなります。
以上の理由より、LINQを使える場面では積極的に用いるべきでしょう。
説明の前に
LINQにはコレクションやシーケンスといった用語が頻出です。
自信がない方は今一度基本用語をチェックしてみてください!
hanaachiru.hatenablog.com
LINQで使えるクエリ演算子
上のコードでも用いた.Whereや.SelectのようなLINQメソッドはたくさんあります。
これは厳密にはIEnumerable<T>インターフェイスの拡張メソッドです。
どれくらいあるかこれも気になると思うので、GIFを載せておきます。
Whereメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> numbers = Enumerable.Range(1, 5) .Where(s => s <= 3); foreach(int num in numbers) { Debug.Log(num); //1 2 3 } } }
Whereメソッドは引数で指定した条件に一致する要素を検索し、見つかった要素を全て返します。
Selectメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> numbers = Enumerable.Range(1, 5) .Select(n => n * n); foreach(int num in numbers) { Debug.Log(num); //1 4 9 16 25 } } }
Selectメソッドはコレクションのすべての要素に対して何らかの操作をし、新しいコレクションを作ります。
Distinctメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Hoge: MonoBehaviour { private void Start() { var numbers = new List<int> { 1, 2, 3, 4, 5, 4, 3, 2, 1 }; IEnumerable<int> results = numbers.Distinct(); foreach (int num in results) Debug.Log(num); //1 2 3 4 5 } }
Distinctメソッドはコレクションから重複を排除することができます。
OrderByメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Enemy { public string Name { get; set; } public int Hp { get; set; } public int Exp { get; set; } } public class Hoge: MonoBehaviour { private void Start() { var numbers = new List<int> { 1, 2, 3, 4, 5, 4, 3, 2, 1 }; IEnumerable<int> sortedNumbers = numbers.OrderBy(x => x); foreach (int num in sortedNumbers) Debug.Log(num); //1 1 2 3 3 4 4 5 var enemies = new List<Enemy> { new Enemy{ Name = "ももんじゃ" , Hp = 20,Exp = 10 }, new Enemy{ Name = "ドラキー" , Hp = 20,Exp = 20 }, new Enemy{ Name = "スライム" , Hp = 10,Exp = 3 }, new Enemy{ Name = "メタルスライム" , Hp = 5,Exp = 100 }, }; IEnumerable<Enemy> sortedEnemy = enemies.OrderBy(x => x.Exp); foreach (Enemy enemy in sortedEnemy) Debug.Log(enemy.Name); //スライム ももんじゃ ドラキー メタルスライム } }
OrderByメソッドはコレクションを昇順にソートします。
また同様に降順にするOrderByDescendingメソッドも存在します。
Concatメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Hoge: MonoBehaviour { private void Start() { var numbers1 = new List<int> { 1, 2, 3, 4, 5 }; var numbers2 = new List<int> { 4, 3, 2, 1 }; IEnumerable<int> concatedNumber = numbers1.Concat(numbers2); foreach (int num in concatedNumber) Debug.Log(num); //1 2 3 4 5 4 3 2 1 } }
Concatメソッドは2つのコレクションを連結することができます。
Enumerable.Repeatメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> numbers = Enumerable.Repeat(-1, 5); foreach(int num in numbers) { Debug.Log(num); //-1, -1, -1, -1, -1 } } }
Enumerable.Repeatメソッドは同じ値を繰り返すシーケンス(IEnumerable
Enumerable.Rangeメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> numbers = Enumerable.Range(1, 5); foreach(int num in numbers) { Debug.Log(num); //1 2 3 4 5 } } }
Enumerable.Rangeメソッドは連続した数字を生成します。
ToListメソッド・ToArrayメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Hoge: MonoBehaviour { private void Start() { List<int> numbers = Enumerable.Repeat(-1, 20) .ToList(); int[] numbers2 = Enumerable.Repeat(-1, 20) .ToArray(); } }
ToListメソッドはシーケンスをリストに変換します。
同様にToArrayメソッドはシーケンスを配列に変換します。
FirstOrDefaultメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Enemy { public string Name { get; set; } public int Hp { get; set; } public int Exp { get; set; } } public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> numbers = Enumerable.Range(1, 5); int num = numbers.FirstOrDefault(n => n >= 3); Debug.Log(num); //3 var enemies = new List<Enemy> { new Enemy{ Name = "スライム" , Hp = 10,Exp = 3 }, new Enemy{ Name = "ももんじゃ" , Hp = 20,Exp = 10 }, new Enemy{ Name = "ドラキー" , Hp = 20,Exp = 20 }, new Enemy{ Name = "メタルスライム" , Hp = 5,Exp = 100 }, }; Enemy enemy = enemies.FirstOrDefault(n => n.Exp >= 10); Debug.Log(enemy.Name); //ももんじゃ } }
FirstOrDefaultメソッドはコレクションの中で条件に一致する最初の要素を取得します。
また同様に最後の要素を取得するLastOrDefaultメソッドも存在します。
Takeメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> numbers = Enumerable.Range(1, 20); var results = numbers.Where(n => n > 10) .Take(5); foreach(int num in results) { Debug.Log(num); //11 12 13 14 15 } } }
Takeメソッドは指定した個数分の要素を取得します。
もし指定した個数分がないときは取得できるところまで取得します。
TakeWhileメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> selected = Enumerable.Range(1, 20) .TakeWhile(x => x < 10); foreach(int num in selected) { Debug.Log(num); //1 2 3 4 5 6 7 8 9 } } }
TakeWhileメソッドは指定した条件を満たしている間だけ要素を取得し、条件を満たさない要素を見つけた時点で終了します。
SkipWhileメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> selected = Enumerable.Range(1, 20) .SkipWhile(n => n <= 10); foreach(int num in selected) { Debug.Log(num); //11 12 13 14 15 16 17 18 19 20 } } }
SkipWhileメソッドは条件を満たしている間は要素を読み飛ばし、それ以降の要素を取り出します。
Anyメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Enemy { public string Name { get; set; } public int Hp { get; set; } public int Exp { get; set; } } public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> numbers = Enumerable.Range(1, 5); bool exist = numbers.Any(n => n % 5 == 0); Debug.Log(exist); //true var enemies = new List<Enemy> { new Enemy{ Name = "スライム" , Hp = 10,Exp = 3 }, new Enemy{ Name = "ももんじゃ" , Hp = 20,Exp = 10 }, new Enemy{ Name = "ドラキー" , Hp = 20,Exp = 20 }, new Enemy{ Name = "メタルスライム" , Hp = 5,Exp = 100 }, }; bool exist2 = enemies.Any(n => n.Exp % 7 == 0); Debug.Log(exist2); //false } }
Anyメソッドは条件に一致する要素がコレクション内に一つでも存在するかどうか調べられます。
Allメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Enemy { public string Name { get; set; } public int Hp { get; set; } public int Exp { get; set; } } public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> numbers = Enumerable.Range(1, 5); bool isAllPositive = numbers.All(n => n > 0); Debug.Log(isAllPositive); var enemies = new List<Enemy> { new Enemy{ Name = "スライム" , Hp = 10,Exp = 3 }, new Enemy{ Name = "ももんじゃ" , Hp = 20,Exp = 10 }, new Enemy{ Name = "ドラキー" , Hp = 20,Exp = 20 }, new Enemy{ Name = "メタルスライム" , Hp = 5,Exp = 100 }, }; bool isAllPositive2 = enemies.All(n => n.Exp > 50); Debug.Log(isAllPositive2); //false } }
Allメソッドはコレクション内のすべての要素が条件を満たしているかどうかを調べられます。
SequenceEqualメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> numbers1 = Enumerable.Range(1, 5); IEnumerable<int> numbers2 = Enumerable.Range(1, 5); bool equal = numbers1.SequenceEqual(numbers2); Debug.Log(equal); //true } }
SequenceEqualメソッドは2つのコレクションの要素が等しいか調べることができます。
ただ同じオブジェクトを参照しているかどうかではないことに注意してください。
Countメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Enemy { public string Name { get; set; } public int Hp { get; set; } public int Exp { get; set; } } public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> numbers = Enumerable.Range(1, 5); int count = numbers.Count(n => n <= 3); Debug.Log(count); //3 var enemies = new List<Enemy> { new Enemy{ Name = "スライム" , Hp = 10,Exp = 3 }, new Enemy{ Name = "ももんじゃ" , Hp = 20,Exp = 10 }, new Enemy{ Name = "ドラキー" , Hp = 20,Exp = 20 }, new Enemy{ Name = "メタルスライム" , Hp = 5,Exp = 100 }, }; double count2 = enemies.Count(n => n.Exp <= 50); Debug.Log(count2); //3 } }
Countメソッドはコレクションの中に条件に一致する要素数を調べられます。
Averageメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Enemy { public string Name { get; set; } public int Hp { get; set; } public int Exp { get; set; } } public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> numbers = Enumerable.Range(1, 5); double average = numbers.Average(); Debug.Log(average); //3 var enemies = new List<Enemy> { new Enemy{ Name = "スライム" , Hp = 10,Exp = 3 }, new Enemy{ Name = "ももんじゃ" , Hp = 20,Exp = 10 }, new Enemy{ Name = "ドラキー" , Hp = 20,Exp = 20 }, new Enemy{ Name = "メタルスライム" , Hp = 5,Exp = 100 }, }; double averageExp = enemies.Average(x => x.Exp); Debug.Log(averageExp); //33.25 } }
Averageメソッドはコレクションの平均値を求めることができます。
Sumメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Enemy { public string Name { get; set; } public int Hp { get; set; } public int Exp { get; set; } } public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> numbers = Enumerable.Range(1, 5); int sum = numbers.Sum(); Debug.Log(sum); //15 var enemies = new List<Enemy> { new Enemy{ Name = "スライム" , Hp = 10,Exp = 3 }, new Enemy{ Name = "ももんじゃ" , Hp = 20,Exp = 10 }, new Enemy{ Name = "ドラキー" , Hp = 20,Exp = 20 }, new Enemy{ Name = "メタルスライム" , Hp = 5,Exp = 100 }, }; int sumExp = enemies.Sum(x => x.Exp); Debug.Log(sumExp); //133 } }
Sumメソッドはコレクションの合計を求めることができます。
Maxメソッド・Minメソッド
using UnityEngine; using System.Collections.Generic; using System.Linq; public class Enemy { public string Name { get; set; } public int Hp { get; set; } public int Exp { get; set; } } public class Hoge: MonoBehaviour { private void Start() { IEnumerable<int> numbers = Enumerable.Range(1, 5); int max = numbers.Max(); Debug.Log(max); //5 var enemies = new List<Enemy> { new Enemy{ Name = "スライム" , Hp = 10,Exp = 3 }, new Enemy{ Name = "ももんじゃ" , Hp = 20,Exp = 10 }, new Enemy{ Name = "ドラキー" , Hp = 20,Exp = 20 }, new Enemy{ Name = "メタルスライム" , Hp = 5,Exp = 100 }, }; int minExp = enemies.Min(x => x.Exp); Debug.Log(minExp); //3 } }
Maxメソッド・Minメソッドはそれぞれコレクションの最大値・最小値を求めます。
+α
LINQには遅延実行,即時実行という性質があります。
ここでは紹介しませんが、是非調べてみてください。
さいごに
お疲れ様です。
想像以上にボリュームがすごくなってしまい、15000文字オーバーというヤバイ記事になってしまいました笑
ただLINQを使うことはたくさんのメリットがあるので、是非試してはどうでしょうか。