foreach Statement
The C# foreach
statement is not resolved to a foreach
statement in the IL code. Instead, the C# compiler converts the
foreach statement to methods and
properties of the IEnumerable interface.
Here’s a simple foreach statement to
iterate all elements in the persons
array and to display them person by person.
The foreach statement is
resolved to the following code segment. First, the GetEnumerator() method is invoked to get an
enumerator for the array. Inside a while
loop - as long as MoveNext() returns
true - the elements of the array are
accessed using the Current property:
yield Statement
C# 1.0 made it easy to iterate through
collections by using the foreach
statement. With C# 1.0, it was still a lot of work to create an
enumerator. C# 2.0 adds the yield
statement for creating enumerators easily.
yield return returns one
element of a collection and moves the position to the next element,
yield break stops the iteration.
The next example shows the implementation of a
simple collection using the yield return
statement. The class HelloCollection
contains the method GetEnumerator(). The
implementation of the GetEnumerator()
method contains two yield return
statements where the strings Hello and
World are returned.
|
|
Important |
A method or property that contains yield
statements is also known as iterator
block. An iterator block must be declared
to return an IEnumerator or IEnumerable interface. This block may contain
multiple yield return or yield break statements; a return statement is not allowed.
|
Now it is possible to iterate through the
collection using a foreach
statement:
With an iterator block the compiler generates a
yield type, including a state machine,
as shown with the following code segment. The yield type implements
the properties and methods of the interfaces IEnumerator and IDisposable. In the sample, you can see the yield
type as the inner class Enumerator. The
GetEnumerator() method of the outer
class instantiates and returns a new yield type. Within the yield
type, the variable state defines the
current position of the iteration and is changed every time the
method MoveNext() is invoked.
MoveNext() encapsulates the code of the
iterator block and sets the value of the current variable so that the Current property returns an object depending on the
position.
Now using the yield
return statement it is easy to implement a class that allows
iterating through a collection in different ways. The class
MusicTitles allows iterating the titles
in a default way with the GetEnumerator() method, in reverse order with the
Reverse() method, and to iterate through
a subset with the Subset() method:
The client code to iterate through the string array
first uses the GetEnumerator() method
that you don’t have to write in your code as this one is used by
default. Then the titles are iterated in reverse, and finally a
subset is iterated by passing the index and number of items to
iterate to the Subset() method:
With the yield statement you can also do more
complex things, for example returning an enumerator from yield
return.
With the TicTacToe game you have nine fields where
the players alternating put a cross or a circle. These moves are
simulated by the GameMoves class. The
methods Cross() and Circle() are the iterator blocks for creating
iterator types. The variable cross and
circle are set to Cross() and Circle()
inside the constructor of the GameMoves
class. By setting these fields the methods are not invoked, but set
to the interator types that are defined with the iterator blocks.
Within the Cross() iterator block,
information about the move is written to the console and the move
number is incremented. If the move number is higher than 9, the
iteration ends with yield break;
otherwise, the enumerator object of the cross yield type is returned with each iteration. The
Circle() iterator block is very similar
to the Cross() iterator block; it just
returns the circle iterator type with each iteration.
From the client program you can use the class
GameMoves as follows. The first move is
set by setting enumerator to the enumerator type returned by
game.Cross(). enumerator.MoveNext invokes one iteration defined
with the iterator block that returns the other enumerator. The
returned value can be accessed with the Current property and is set to the enumerator variable for the next loop:
The outcome of this program shows alternating moves
until the last move: