UnityのiOSビルドで使うとエラーになるLINQ拡張メソッドのメモ
UnityのiOSビルドでLINQ拡張メソッドを使っていると、実行時に
ExecutionEngineException: Attempting to JIT compile method 'System.Linq.Enumerable:Sum
(System.Collections.Generic.IEnumerable`1 ,System.Func`3 )' while running with --aot-only.
みたいなエラーが出るものと*1、普通に使えるものがある。
で、いちいち選別するのが面倒なので、使える使えないを調べずにLINQのメソッドを避けてたんだけど、やっぱりCountとか使えるものは使いたくなったので、使用頻度の高めのものだけ調べた。
調べている途中で、同じ System.Linq.Enumerable のメソッドでも、classのListでは使えなくても、配列やstructのListだと使えたりするものもあるようで、やっぱり使えるかとか調べないでLINQは避けたほうがいい気もしてきてしまった。
ざっと調べた限りではこんな感じだった。
メソッド名 | 配列 | List<struct> | List<class> |
All | ○ | ○ | ○ |
Any | ○ | ○ | ○ |
Average | × | × | ○ |
Count | ○ | ○ | ○ |
First, Last | ○ | ○ | ○ |
Max, Min | ○ | ○ | × |
OrderBy | ○ | ○ | ○ |
Sum | ○ | ○ | × |
(Unity version 4.1.3f3 で確認)
調べるのに使ったソース。OnGUIで作ったボタンを押して、ExecutionEngineExceptionが出るか出ないかを目視で確認してる。
using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Linq; public class CHoge { public int i; public CHoge(int i) { this.i = i; } } public struct SHoge { public int i; public SHoge(int i) { this.i = i; } } public class LinqRunTest : MonoBehaviour { int[] alist; List<CHoge> clist; List<SHoge> slist; Vector2 scrollPos; void Start() { alist = new int[10]; clist = new List<CHoge>(); slist = new List<SHoge>(); for (var i = 0; i < 10; ++i) { alist[i] = i; slist.Add(new SHoge(i)); clist.Add(new CHoge(i)); } scrollPos = Vector2.zero; } void OnGUI() { var x = 0; var y = 0; var w = 100; var h = 100; scrollPos = GUI.BeginScrollView(new Rect(0,0, Screen.width, Screen.height), scrollPos, new Rect(0,0, Screen.width, h*20)); if (GUI.Button(new Rect(x+w*0,y,w,h), "Sum")) {Debug.Log("s Sum : " + slist.Sum(v => v.i*2));} if (GUI.Button(new Rect(x+w*1,y,w,h), "Sum")) {Debug.Log("c Sum : " + clist.Sum(v => v.i*2));} if (GUI.Button(new Rect(x+w*2,y,w,h), "Sum")) {Debug.Log("a Sum : " + alist.Sum(v => v*2));} y+=w; if (GUI.Button(new Rect(x+w*0,y,w,h), "All")) {Debug.Log("s All : " + slist.All(v => v.i > 0));} if (GUI.Button(new Rect(x+w*1,y,w,h), "All")) {Debug.Log("c All : " + clist.All(v => v.i > 0));} if (GUI.Button(new Rect(x+w*2,y,w,h), "All")) {Debug.Log("a All : " + alist.All(v => v > 0));} y+=w; if (GUI.Button(new Rect(x+w*0,y,w,h), "Any")) {Debug.Log("s Any : " + slist.Any(v => v.i >= 8));} if (GUI.Button(new Rect(x+w*1,y,w,h), "Any")) {Debug.Log("c Any : " + clist.Any(v => v.i >= 8));} if (GUI.Button(new Rect(x+w*2,y,w,h), "Any")) {Debug.Log("a Any : " + alist.Any(v => v >= 8));} y+=w; if (GUI.Button(new Rect(x+w*0,y,w,h), "Average")) {Debug.Log("s Average : " + slist.Average(v => v.i*2));} if (GUI.Button(new Rect(x+w*1,y,w,h), "Average")) {Debug.Log("c Average : " + clist.Average(v => v.i*2));} if (GUI.Button(new Rect(x+w*2,y,w,h), "Average")) {Debug.Log("a Average : " + alist.Average(v => v*2));} y+=w; if (GUI.Button(new Rect(x+w*0,y,w,h), "Count")) {Debug.Log("s Count : " + slist.Count(v => v.i % 2 == 0));} if (GUI.Button(new Rect(x+w*1,y,w,h), "Count")) {Debug.Log("c Count : " + clist.Count(v => v.i % 2 == 0));} if (GUI.Button(new Rect(x+w*2,y,w,h), "Count")) {Debug.Log("a Count : " + alist.Count(v => v % 2 == 0));} y+=w; if (GUI.Button(new Rect(x+w*0,y,w,h), "First")) {Debug.Log("s First : " + slist.First(v => v.i > 5));} if (GUI.Button(new Rect(x+w*1,y,w,h), "First")) {Debug.Log("c First : " + clist.First(v => v.i > 5));} if (GUI.Button(new Rect(x+w*2,y,w,h), "First")) {Debug.Log("a First : " + alist.First(v => v > 5));} y+=w; if (GUI.Button(new Rect(x+w*0,y,w,h), "Last")) {Debug.Log("s Last : " + slist.Last(v => v.i < 5));} if (GUI.Button(new Rect(x+w*1,y,w,h), "Last")) {Debug.Log("c Last : " + clist.Last(v => v.i < 5));} if (GUI.Button(new Rect(x+w*2,y,w,h), "Last")) {Debug.Log("a Last : " + alist.Last(v => v < 5));} y+=w; if (GUI.Button(new Rect(x+w*0,y,w,h), "Max")) {Debug.Log("s Max : " + slist.Max(v => v.i*2));} if (GUI.Button(new Rect(x+w*1,y,w,h), "Max")) {Debug.Log("c Max : " + clist.Max(v => v.i*2));} if (GUI.Button(new Rect(x+w*2,y,w,h), "Max")) {Debug.Log("a Max : " + alist.Max(v => v*2));} y+=w; if (GUI.Button(new Rect(x+w*0,y,w,h), "Min")) {Debug.Log("s Min : " + slist.Min(v => v.i*2));} if (GUI.Button(new Rect(x+w*1,y,w,h), "Min")) {Debug.Log("c Min : " + clist.Min(v => v.i*2));} if (GUI.Button(new Rect(x+w*2,y,w,h), "Min")) {Debug.Log("a Min : " + alist.Min(v => v*2));} y+=w; if (GUI.Button(new Rect(x+w*0,y,w,h), "OrderBy")) {Debug.Log("s OrderBy : " + slist.OrderBy(v => -v.i));} if (GUI.Button(new Rect(x+w*1,y,w,h), "OrderBy")) {Debug.Log("c OrderBy : " + clist.OrderBy(v => -v.i));} if (GUI.Button(new Rect(x+w*2,y,w,h), "OrderBy")) {Debug.Log("a OrderBy : " + alist.OrderBy(v => -v));} y+=w; GUI.EndScrollView(); } }
ソースを見ればわかるとは思うが、基本的に引数に簡単な Func(T, int) を渡すパターンしか調べてないし、至極シンプルなstructとclassでしか確認してないので、もっと厄介なパターンで駄目になるものもあるのかもしれない。が、そんなんまでイチイチ調べてられん。
余談
classだとエラーが出るが、配列とstructならエラーが出ないものなら、値のイテレーターにしてからSumを呼べばエラーは出ない。
(from v in clist select v.i * 2).Sum()
Sumってコードにある分、可読性は多少はマシな気はするが……。自前の拡張メソッドで System.Linq.Enumerable.Sum とかを上書きできればいいんだけど、そもそもUnityってnamespace使えないからなぁ……
と思ったら、Unity4.0 で namespaceは使えるようになってた。*2
Scripting: MonoBehaviours can now be inside namespaces.
http://unity3d.com/unity/whats-new/unity-4.0
と喜んだのもつかの間、拡張メソッドの上書きってC#でできるのか?というところで詰んだ。同名の拡張メソッドの優先順位ってどうなってんだろう。
*1:Unity3.x系の時ってこんなにわかりやすい例外メッセージ出てたっけ……。突然止まって原因がなかなか見つからなくて悪戦苦闘したような記憶があったんだけど……
*2:こっちのドキュメントにはまだ"Don't use namespaces."って書いてあるけどもー