A feature of C# that I frequently use to process collections is the foreach statement. foreach is just syntactic sugar for making it easier to iterate over collections. I’d like to delve a bit into what’s going on under the hood.
Syntactic sugar
Syntactic sugar is syntax in a language that makes it easier to read. It makes the language “sweeter” to work with (ha!). While syntactic sugar is great for increasing readability in a language, it’s also good to know what’s going on underneath.
foreach
foreach allows you to pass in each individual element of a collection into a code block and execute that block of code with the element. In this way, you iterate over the collection. I’d like to show you a code sample to make what I mean a bit clearer.
Let’s take a look at the following method:
private void PrintNumbers()
{
IEnumerable<int> numbers = new List<int> { 1, 2, 3 };
foreach(int number in numbers)
{
Console.WriteLine(String.Format("The number is {0}", number));
}
}
First, a list of integers is declared. This is pretty straightforward. The next line is the start of the foreach statement. The format of a foreach statement is such:
foreach(type element in collection) inner-statement
type declares the type of element being passed into the code block. Because we’re working with a list of integers, the type is int.
element specifies the individual element that is used in the inner-statement. The scope of the element is only within the inner-statement for the current iteration. For example, when the foreach block is executed the first time for the number 1, the variable “number” has a value of 1. The second time around, it has the value of 2. The third and last time around, it has the value of 3.
collection specifies the collection being iterated over. In this case, “numbers” is being passed in as the collection. In order for the statement to be syntactically valid, the type of collection must implement the IEnumerable interface OR satisfy the following criteria:
1) collection must have a method on it with the following signature:
public T GetEnumerator()
where T is a class type, struct type, or interface type.
2) The type T returned by GetEnumerator() must have a method with the following signature:
public bool MoveNext()
3) The type T returned by GetEnumerator() must have a property with the following signature:
public [type of *collection*] Current
that allows the current value to be read.
inner-statement is the statement (which can obviously be composed of multiple statements) that gets executed once for each element in the collection. In this case, the inner-statement is:
Console.WriteLine(String.Format("The number is {0}", number));
When this method executes, the following output will be printed to the console:
The number is 1
The number is 2
The number is 3
Under the hood
But like I said before, this is syntactic sugar. What’s really going on?
The compiler will perform a transformation, or expansion, on the foreach statement. Whether or not collection implements IEnumerable or meets the criteria previously mentioned will determine the type of expansion that takes place. Since the example is using a collection implementing IEnumerable, I will show that expansion.
This is what the method looks like after the expansion takes place:
private void PrintNumbers()
{
IEnumerable<int> numbers = new List<int> { 1, 2, 3 };
IEnumerator enumerator =
((System.Collections.IEnumerable)(numbers)).GetEnumerator();
try {
while (enumerator.MoveNext()) {
int element = (int)enumerator.Current;
Console.WriteLine(String.Format("The number is {0}", element));
}
}
finally {
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
}
}
Basically, an enumerator is created that makes use of the methods on it that allow it to access each element in the collection. After the work has been done for a given element, it moves onto the next one. After all the elements have been iterated over, the enumerator is disposed of to free up memory.
These two code blocks effectively achieve the same result. But wouldn’t you agree that the foreach statement pre-expansion looks a lot more pleasant and clearer?
Conclusion
We discussed how syntactic sugar makes it easier to read a given programming language. We also looked at the foreach statement in C# as an example of syntactic sugar and what really goes on under the hood. You can read more about foreach at Microsoft’s website.