正直プログラム経験がまだまだ浅いから間違いはあるかも。ご指摘下さればありがたいです。
※すみません。訳あってList編の途中のまま投稿しています。ご了承ください。月曜日にはまた時間が取れそうなので、月曜日には書き終える予定です。
きっかけ
https://akinow.livedoor.blog/archives/52474053.html
そもそものきっかけは、上記の記事でforeachの使用を控えるような内容を見たからだった。
(後々分かったが、これはUnity5.5で解決済みだった)
軽量化に力を入れていきたい身としては、重いとかメモリの効率が~なんて話は聞き捨てならない。
たとえ解決済みだとしても、気になる。一度気になりだしたら止まらない。
・・・まあ、結果。ハマりましたよ。
見事にループ処理の沼にハマったので、同じような疑問を感じた人と、未来の自分用のメモとして、結局何がどういいのかまとめてみる。
コンパイル結果は、sharplab様にお世話になりました。
配列編
コンパイル用に以下のようなコードを作成したので、コンパイルしてみる。
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型の変数を一つ用意して、ループ毎に足し、配列の大きさより大きくなったら、whileから抜けるだけのようだ。
まあ、強いて言えばここで配列が参照されているぐらいか。
int[] array2 = TestArray;
ちなみに配列は参照型なので、コピーはされない。上記のように書いても、渡されるのは配列のアドレス情報のみ。この程度なら問題はない。
もしも知らない!気になった!って方はその辺は論点がずれてしまうので、以下サイト様がわかりやすいと思う。
https://ufcpp.net/study/csharp/oo_reference.html#type-category
さて、結果としては、やってることが同じならforを使うメリットは無い。
そもそも、foreachは糖衣構文( 要は読み書きのしやすさを重要視されている処理 )だから、優先してforeachを使いたい。
List編
Listを回す際に、どちらを使用すべきか。
配列に対してのコンパイル結果は、分かりやすく非常に簡単な結果であったが、ListはEnumeratorというものが関わってくるため、少々厄介だったりする。
まずはコンパイルしてみる。内容はさっきと同じ。
( ListにはLINQのCount()関数と、List内のcount変数があるが、変数でやる場合の方が個人的に多いし、こちらの方が処理が速いためこちらを使用している。他にも理由はあるけど後述する。)
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型のメソッドだ。
内部では主に、
↧