そもそものきっかけは、上記の記事でforeachの使用を控えるような内容を見たからだった。
(後々分かったが、これはUnity5.5で解決済みだった)
軽量化に力を入れていきたい身としては、重いとかメモリの効率が~なんて話は聞き捨てならない。
たとえ解決済みだとしても、気になる。一度気になりだしたら止まらない。
・・・まあ、結果。ハマりましたよ。
見事にループ処理の沼にハマったので、同じような疑問を感じた人と、未来の自分用のメモとして、結局何がどういいのかまとめてみる。
コンパイル結果は、sharplabにお世話になりました。
内部分のコードなんかはactual implementationを参照させていただきました。
##配列編
コンパイル用に以下のようなコードを作成したので、コンパイルしてみる。
sumに配列の中身を足していく処理だ。
コンパイルしたいだけなので、sumがリセットされてないとか、配列に何も入っていないとか。
その辺は見逃してほしい・・・。
int[] TestArray = new int[100];
int sum = 0;
//forの処理
for (int i = 0; i < TestArray.Length; i++)
{
sum += TestArray[i];
}
//foreachの処理
foreach (int i in TestArray)
{
sum += i;
}
↓ コンパイル
int[] TestArray = new int[100];
int sum = 0;
//forの処理
int num = 0;
while (num < TestArray.Length)
{
sum += array[num];
num++;
}
//foreachの処理
int[] array2 = TestArray;
int num2 = 0;
while (num2 < array2.Length)
{
int num3 = array2[num2];
sum += num3;
num2++;
}
int[] array2 = TestArray;
ちなみに配列は参照型なので、コピーはされない。上記のように書いても、渡されるのは配列のアドレス情報のみ。この程度なら問題はない。
もしも知らない!気になった!って方はその辺は論点がずれてしまうので、以下サイト様がわかりやすいと思う。
さて、結果としては、やってることが同じならforを使うメリットは無い。 そもそも、foreachは糖衣構文( 要は読み書きのしやすさを重要視されている処理 )だから、**優先してforeachを使いたい。**https://ufcpp.net/study/csharp/oo_reference.html#type-category
##List編
Listを回す際に、どちらを使用すべきか。
配列に対してのコンパイル結果は、分かりやすく非常に簡単な結果であったが、ListはEnumeratorというものが関わってくるため、少々厄介だったりする。
まずはコンパイルしてみる。内容はさっきと同じ。
List<int> TestList = new List<int>();
int sum = 0;
//forの処理
for (int i = 0; i < TestList.Count; i++)
{
sum += TestList[i];
}
//foreachの処理
foreach (int i in TestList)
{
sum += i;
}
↓ コンパイル
List<int> list = new List<int>();
int sum = 0;
//forの処理
int num = 0;
while (num < list.Count)
{
sum += list[num];
num++;
}
//foreachの処理
List<int>.Enumerator enumerator = list.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
int current = enumerator.Current;
sum += current;
}
}
finally
{
((IDisposable)enumerator).Dispose();
}
ちょっとごちゃごちゃしてきたので、混乱するかもしれないが順番に紐解いていこう。
まず、forの部分は先ほどの配列とやり方は変わっていないので、言わなくても分かると思う。
問題はforeachの方だろう。
まず、Listは幾つかのインターフェースを継承しているのだが、以下の行でその中の一つであるIEnumerable内の唯一のメソッドであるGetEnumerator()というメソッドにアクセスしている。
List<int>.Enumerator enumerator = list.GetEnumerator();
####GetEnumerator()が一体何をするメソッドなのか。
では、GetEnumerator()とはなんなのか。
簡単にまとめるとGetEnumerator()というメソッドは、List内にあるEnumeratorという構造体に自身の情報を設定して返すだけのメソッドだ。
もう少し詳しく言うならば、GetEnumerator()を呼び出した際に、Enumeratorのコンストラクタにアクセスし、渡されたList、初期index、渡された時点での_version、currentを設定し、設定したEnumeratorを返すメソッドだ。
####IEnumerator内のメソッド
次に以下の処理に注目してもらいたい。
while (enumerator.MoveNext())
{
int current = enumerator.Current;
sum += current;
}
次に疑問に思うのは、MoveNext()とは何者なのかという点だろう。
MoveNext()はEnumeratorが継承するインターフェースの一つであるIEnumerator内のbool型のメソッドだ。
こいつは簡単に言うと、List内の要素数が超えたらループを抜けて、要素を超えていない場合はループを続けるという処理が内部的に行われている。
もう少し深掘りしてみよう。
MoveNext()を覗いてみると、以下のようになっている。
public bool MoveNext()
{
List<T> localList = list;
if (version == localList._version && ((uint)index < (uint)localList._size))
{
current = localList._items[index];
index++;
return true;
}
return MoveNextRare();
}
private bool MoveNextRare()
{
if (version != list._version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
index = list._size + 1;
current = default(T);
return false;
}
これだけでは、訳が分からないかもしれないので、順番に見ていこう。
まず、以下の部分で、**Listのバージョンが合っているか。現在探索している要素がList内の要素数を超えていないか。**を調べている。
もし、条件が合っていた場合、current変数に現在の要素を設定し、探索位置をずらし、ループは続くのでtrueを返している。
if (version == localList._version && ((uint)index < (uint)localList._size))
{
current = localList._items[index];
index++;
return true;
}
では、条件が満たされなかった場合はどうなるのか。
その場合は、MoveNextRare()に入る。
MoveNextRare()の処理は、まずversionが合っているか確認し、合っていなかった場合はエラーを吐く。
もしあっていた場合は、currentを初期化してfalseを返すことでループを終了します。
private bool MoveNextRare()
{
if (version != list._version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
index = list._size + 1;
current = default(T);
return false;
}
これがだいたいのforeachのList探索の概要です。
では、結局どっちをつかうのがいいのか?
以下のサイト様を見た所、速度面ではforの方がforeachよりも早い。
メモリ面から見ても、GetEnumerator()でEnumeratorを生成している分、forの方がいいように感じられるが、私個人の見解としては、そこまで気にするほどではないように感じた。
よっぽど、速度を重要視する場合や、メモリ効率を考えたい!といった場合でない限りは、糖衣構文であるforeachの使用を避ける程ではないように思う。
まあ、この程度であれば好みの範疇かな?
##結果
2021/04/21修正:配列の結果がforになってました!!!ごめんんささい!!
配列:foreach
List:基本的にforeachでいい。よっぽど気にするようであればfor
以上!
ご指摘、ご意見おまちしておりまs・・・
めtyくちゃ参考にさせていただきましたサイト様