Internals of loops (While, For and ForEach)






4.76/5 (7 votes)
Internals of loops (While, For and ForEach)
Practically speaking, a loop is the primary building block of a program. We use loop to repeat a set of actions for a certain interval. Now if you think of these intervals, it could be a traversal from one number (called as start index) to another number (called as end index). Very often or probably out of 10 such loops, eight times you loop a collection such that you start from 0 and loop until you point to the end of the sequence.
In C# (and VB.NET) we use while
, do
-while
, for
and foreach
loop to loop through a set of instructions. In this post, I will try to demonstrate the basic loops for a while and later on take on a bit about foreach
loop and its requirement and finally go deep into its internals.
The Basics
C# comes with three basic types of loops.
- While Loop: This kind of loop runs while the condition is satisfied.
while (x >= 0) { }
- do-while: This loop runs at least once while the condition is satisfied.
do { } while (x >= 10);
- for: This loop has three sections, index declaration, condition and increment/decrement section. For each call to the instruction, the index is incremented and checked with the condition.
for (int i = 0; i < 10; i++) { }
These are the most basic loops and I hope you already know them and have come across it in your life. .NET introduces another loop called Foreach
loop which specially works on an IEnumerable
. Each collection in .NET somehow implements IEnumerable
and hence can be used as a part of foreach
loop. Hence every sequence (or whatever class) when implemented an IEnumerable
will have the provision to enumerate through values using foreach
.
var enumerable = Enumerable.Range(1, 100);
foreach (var e in enumerable)
{
}
In the above code, the instruction within the scope will loop through until the enumerable has value. Now you might wonder why this interface is at all required? Why do we need to implement it? What exactly happens inside these loops. Let's demonstrate these points.
The Internals
Before going further with the internals of loop, let me clear out one fact which you always remember. Goto
is a construct(keyword) in C# that allows you to unconditionally transfer the control from one place to another. The IL equivalent for goto
statement is br.s
which takes just an instruction line number to transfer the control to a place. Another small instruction brtrue.s
which evaluates the loaded object and moves the control to the instruction line only when the evaluated value turns out to be true
.
Hence to summarize:
br.s
-> Unconditionally moves the controlbrtrue.s
-> Moves the control if loaded value istrue
Now let's start looking at the internals one by one.
While, do - while Loop
int i=10;
while (x >= 0)
{
}
If you look into the IL instruction set, it creates an integer variable x (which we have declared in code) and a placeholder for boolean variable. The L_0004
instruction is actually a goto
equivalent which moves the control over to the location specified. It evaluates and stores the result of equality of x with 0; clt and ceq specifies the less than and equal respectively.
Based on the result, L_0011
is evaluated and control moves to specified instruction line accordingly.
So basically while
loop is a sequence of goto
statements between an instruction statements to perform a loop. The instruction between L_0005 - L_0008
is the actual action (one inside curl braces of the loop).
Do
-while
loop is very similar to while
loop, with the only difference being the absence of instruction L_0004
, and hence lets it pass through the action.
For Loop
for (int i = 0; i < 10; i++)
{
}
The instruction set for the for
loop is also very similar to that of while
. The for
loop creates the index in the first place and stores in zero'th local (L_0002
). The goto
statement ensures the condition is evaluated and hence moves to L_000b
. After the condition is evaluated, the L_0012
sends back the control to L_0005
. The L_0005
- L_0006
represents the action block (one inside the curl braces) and eventually after that, it increments the index element by 1
and continues.
We kept the instruction set simple using no code inside the action block. The only difference of for
loop with that of while
is the extra instruction to store local index and increment it after the execution of action each time.
For-Each Loop
To understand foreach
loop, you first need to understand the basics of IEnumerable
. In .NET, a collection implementing IEnumerable
must implement a method called GetEnumerator
which returns an IEnumerator
.
IEnumerator
actually represents the cursor which points to a certain logical index of the collection. Let's see the IEnumerator
interface.
public interface IEnumerator<out T>
{
T Current { get; }
bool MoveNext();
void Reset();
}
Hence the IEnumerator
actually keeps track of the state of the collection, which allows you to use the method MoveNext
to point to the next location, Current
holds the current value of the collection and Reset
allows you to re-initialize the cursor.
IEnumerable
on the other hand wraps the enumerator into it such that any collection which implements it, can use the foreach
loop. I will discuss in-depth about how to generate Enumerable
in my next post.
Now let us take an example to demonstrate what is happening inside a foreach
loop:
IEnumerable<int> enumerable = Enumerable.Range(1, 100);
foreach (int e in enumerable)
{
}
The above code actually generates an IEnumerable
(collection) of 99 elements from 1 through 100. We loop through the enumerable to get each integer and perform the action on it. Now this is not that simple. Foreach
loop is actually broken into the following code after it is been compiled to IL.
var enumerable = Enumerable.Range(1, 100);
IEnumerator<int> enumerator = enumerable.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
int element = enumerator.Current;
//here goes your action instructions
}
}
finally
{
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
}
I think this looks great to you. Yaah, a foreach
loop is actually an abstraction to what we see above. It first finds the enumerator using GetEnumerator
from the enumerable, uses MoveNext
to loop through the instructions and in the finally
block, it tries to dispose the enumerator.
Similar to what I showed in the code above, you can see the IL to correspond to the same rule. The try
/ finally
ensures that enumerator is disposed after the foreach
loop is complete or even an exception occurs.
Conclusion
So, as you saw, most of the IL instructions are goto
statements, it is not a good idea or not recommended to write goto
statement in your code. As suggested, it is fine with goto
as soon as the compiler generates this for you, but it will be horrible if the language does consist of a large number of unconditional jumps. So beware.
I hope you like this post, I will continue with IEnumerable
in my next post. Stay tuned.
Thank you for reading.