Anatomy Of A For-loop In C#

Understanding the underlying logic that makes up this useful construct.

Introduction

Looping through data, or iterating, is a necessary task. It sounds simple but is fairly often done in a questionable manner.

Each company, developer and application generally has their own set of rules or metrics which will govern the use of a for-loop. If is up to the developer to identify which version is best for their application.

The Parts That Make Up A For-loop

In general, the for-loop is identified by the "for" keyword followed by 3 optional blocks of code, each separated by a semi-colon;

for(initialisation; termination condition; iterator)
{
  // Logic goes here....
}

An example would be written like this

for(int i=0; i < 10; i++)
{
  // Logic goes here....
}

Initialisation Block

Inside we initialise the variable we often refer to as the index. In this case we have written int i=0. We could have declared i above the for-loop and simply set it's initial value in the initialisation block.

Or, maybe even more dramatic, declared and initialised the variable above the for-loop, and left this section empty.

int i=0;
for(; i< 10; i++)
{
  // Logic goes here...
}

Termination Condition Block

Ideally, this should consist of the conditions that are run if we want the loop to stop running.

Iterator Block

Finally, the expectation here is we indicate how the index variable will change at the end of the for-loop. However, a more generalised definition would be this is the block of code that is run at the end of the for-loop.

Intermediate Language Representation

If we take the IL code for our for-loop above, we can get a better picture of how the blocks fit together.

The C# code was:

for(int i=0; i < 10; i++)
{
  // Logic goes here....
}

The IL below was generated in JetBrains Rider, using a version of the .Net 6 SDK and version 10 of the C# language. This is the IL code generated at compile time and may differ from the IL code run on the CPU after a few iterations.

// The initialisation block. 0 is loaded into i
IL_00c4: ldc.i4.0
IL_00c5: stloc.s      i

// There is a jump to the termination conditions.
// If the value of i was set to 11, it would be greater than 10
//  and our for-loop would not run.
IL_00c7: br.s         IL_00cf

// The first line in our inner loop would be here.
// Since our loop is empty, there is nothing to run.

// The iterator.
// The index i is loaded into memory and incremented.
// The code falls through to the termination block where
//  i is checked to see if its in range.
IL_00c9: ldloc.s      i
IL_00cb: ldc.i4.1
IL_00cc: add
IL_00cd: stloc.s      i

// The termination block.
// The value of i is checked here. If i is less than 10
//  the code jumps to the start of the first line in our inner loop.
IL_00cf: ldloc.s      i
IL_00d1: ldc.i4.s     10 // 0x0a
IL_00d3: blt.s        IL_00c9
// end of loop

An Alternative Implementation

If you want that in C# code, we can write it using labels and use goto.

int i = 0;
goto TerminationBlock;
FirstLine:
Iterator:
    i++;

TerminationBlock:
if (i < 10)
{
    goto FirstLine;
}

The IL code is exactly the same, however there are more lines of C# code to write, and way more places to make a mistake.

Conclusion

In the following posts we will take a look at places where we can make some improvements for performance, as well as ways to make your code harder to read.