C#のforeachの仕組み(2) forとの比較コードの続きです。
Niigata.NET 3.0に参加して、forと比較して、foreachをブラックボックスに感じたので、少し整理したい。という話です。
Niigata.NET 3.0に参加してきました
概要
前回、コメントにて@albireoに『ArrayのかわりにListを使えばお手軽に自前Enumeratorを試せますよ。』と、教えてもらったので、早速実装して確認してみました。
結論を言うと、実装面ではモヤモヤがかなりスッキリしました。
ありがとうございました。
IEnumeratorを独自実装
はじめに、IEnumeratorを独自実装したListの拡張クラスを作成しました。
※余談ですが、netcoreのlist実装が見当たらないのですが、どちらにおいででしょうか・・・?
https://github.com/dotnet/corefx/tree/master/src/System.Collections/src/System/Collections/Generic
Implement_ListWithLoopConditions
/// <summary>
/// ref https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs
/// BTW, where class list in https://github.com/dotnet/corefx ?
/// </summary>
class ListWithLoopConditions<T> : List<T>
{
public int? LoopStart { get; set; }
public int? LoopEnd { get; set; }
public int? LoopIncrement{ get; set; }
public new IEnumerator<T> GetEnumerator()
{
return new EnumeratorWithLoopConditions(this, LoopStart, LoopEnd, LoopIncrement);
}
struct EnumeratorWithLoopConditions : IEnumerator<T>
{
private List<T> list;
private T current;
private int start;
private int index;
private int end;
private int increment;
public EnumeratorWithLoopConditions(List<T> list, int? start, int? end, int? increment)
{
this.list = list;
this.current = default(T);
this.start = start ?? 0;
this.index = this.start;
this.end = end ?? list.Count;
this.increment = increment ?? 1;
}
public bool MoveNext()
{
if ((start < end && index < end) || (start > end && index > end))
{
current = list[index];
index += increment;
return true;
}
return false;
}
public void Reset()
{
index = start;
current = default(T);
}
public T Current => current;
object IEnumerator.Current => current;
public void Dispose() { }
}
}
動作確認
上記の拡張Listを使用して確認してみます。
まず、通常ループの確認。
Implement_All_loop
class Program
{
static void Main()
{
var numbers = new ListWithLoopConditions<int>() { 0, 1, 2, 3, 4, 5, 6 };
Console.WriteLine("All loop by for");
for (int i = 0; i < numbers.Count; i++)
{
Console.WriteLine(numbers[i]);
}
Console.WriteLine("All loop by foreach with loop conditions");
foreach (var n in numbers)
{
Console.WriteLine(n);
}
}
}
Result_All_loop
All loop by for
0
1
2
3
4
5
6
All loop by foreach with loop conditions
0
1
2
3
4
5
6
次に、条件指定付きループの確認。
Implement_Select_loop
class Program
{
static void Main()
{
var numbers = new ListWithLoopConditions<int>() { 0, 1, 2, 3, 4, 5, 6 };
Console.WriteLine("Select loop by for");
for (int i = 1; i < numbers.Count - 1; i += 2)
{
Console.WriteLine(numbers[i]);
}
Console.WriteLine("Select loop by foreach with loop conditions");
numbers.LoopStart = 1;
numbers.LoopEnd = numbers.Count - 1;
numbers.LoopIncrement = 2;
foreach (var n in numbers)
{
Console.WriteLine(n);
}
}
}
Result_Select_loop
Select loop by for
1
3
5
Select loop by foreach with loop conditions
1
3
5
最後に、逆順で条件指定付きループの確認。
Implement_Reverse_select_loop
class Program
{
static void Main()
{
var numbers = new ListWithLoopConditions<int>() { 0, 1, 2, 3, 4, 5, 6 };
Console.WriteLine("Reverse select loop by for");
for (int i = numbers.Count - 1; i > 1; i -= 2)
{
Console.WriteLine(numbers[i]);
}
Console.WriteLine("Reverse select loop by foreach with loop conditions");
numbers.LoopStart = numbers.Count - 1;
numbers.LoopEnd = 1;
numbers.LoopIncrement = -2;
foreach (var n in numbers)
{
Console.WriteLine(n);
}
}
}
Result_Reverse_Select_loop
Reverse select loop by for
6
4
2
Reverse select loop by foreach with loop conditions
6
4
2
まとめ
- foreachは、列挙された集合の各要素に対して、埋め込み文を実施する。
- 集合がどのように列挙されるかについては、System.Collections.IEnumeratorのMoveNext()にて、要素の列挙方法が制御されている。
- 従って、MoveNext()を独自実装すると、任意の列挙が可能である。
- ゆえに、任意で列挙すると、foreachの対象を制御できるため、結果、ループの振る舞いに介入できる。
調べたいこと
- .NetがIEnumeratorを実行する仕組み
- ildasmとDisassembly
スペシャルサンクス
※コメント順