The iterator pattern is an example of patterns of behavior in design patterns (behavioral pattern), a pattern that simplifies communication between objects and is a very easy to understand and use pattern. Simply put, the iterator pattern allows you to get all the elements in the sequence without worrying about whether the type is array,list,linked list or what sequence structure it is. This makes it very efficient to build data pipeline-that is, it can enter processing channels, perform a series of transformations, or filter, and then get the results. In fact, this is the core pattern of LINQ.
In. NET, the iterator pattern is encapsulated by the IEnumerator and IEnumerable and their corresponding generic interfaces. If a class implements the IEnumerable interface, it can be iterated; Calling the GetEnumerator method returns the implementation of the IEnumerator interface, which is the iterator itself. An iterator resembles a cursor in a database, which is a positional record in a data series. Iterators can only move forward, with multiple iterators in the same data sequence manipulating the data at the same time.
Support for iterators has been built into the c#1, and that is the foreach statement. Makes it possible to iterate over the collection more directly and simply than for the FOR Loop statement, the compiler calls the Foreach compiler to invoke the GetEnumerator and MoveNext methods and the current property, and if the object implements the IDisposable interface, the iteration When finished, the iterator is released. But in c#1, implementing an iterator is a relatively cumbersome operation. C#2 makes this work much simpler and saves a lot of work on implementing iterators.
Next, let's look at how to implement an iterator and c#2 for the iterator implementation, and then enumerate the examples of several iterators in real life.
1. C#1: Manual implementation of the cumbersome iterator
Suppose we need to implement a new collection type based on the ring buffer. We will implement the IEnumerable interface, which makes it easy for users to take advantage of all the elements in the collection. We ignore other details and focus only on how to implement the iterator. The collection stores values in an array, and the collection sets the starting point of the iteration, for example, assuming that the set has 5 elements and you can set the starting point to 2, then the iteration output is 2,3,4,0 and the last is 1. To be able to demonstrate simply, we provide a constructor for setting values and starting points. Allows us to iterate through the collection in the following way:
Object[] values = {"a","b","C","D","e"};iterationsample Collection=NewIterationsample (values,3);foreach(ObjectXinchcollection) {Console.WriteLine (x);}
Since we set the starting point to 3, the result of the collection output is D,e,a,b and C, now let's look at how to implement an iterator for the Iterationsample class:
class iterationsample:ienumerable{ Object[] values; Int32 Startingpoint; public Iterationsample (object[] Values , Int32 startingpoint) { values; this . Startingpoint = Startingpoint; public IEnumerator GetEnumerator () { throw new NotImplementedException (); }}
We have not yet implemented the GetEnumerator method, but how to write the logic of the GetEnumerator part, the first is to place the current state of the cursor somewhere. On the one hand, the iterator pattern does not return all the data at once, but the client requests only one data at a time. This means that we want to record the record that the customer is currently requesting into the collection. The C#2 compiler has done a lot of work for the state of the iterator. Now let's take a look at what states to save and where the state exists, imagine that we're trying to save the state in the Iterationsample collection, so that it implements the IEnumerator and IEnumerable methods. At first glance, it may seem, after all, that the data is in the right place, including the starting position. Our GetEnumerator method simply returns this. However, this approach has a very important problem, if the GetEnumerator method is called multiple times, then multiple independent iterators will return. For example, we can use two nested foreach statements to get all possible value pairs. These two iterations need to be independent of each other. This means that the two iterator objects that we need to return each time we call GetEnumerator must remain independent. We can still do this directly in the Iterationsample class through the corresponding function. But our class has multiple responsibilities, and this one backs the principle of single responsibility. So let's create another class to implement the iterator itself. We use the inner classes in C # to implement this logic. The code is as follows:
classiterationsampleenumerator:ienumerator{iterationsample parent;//an iterative object #1Int32 position;//the position of the current cursor #2 InternalIterationsampleenumerator (iterationsample parent) { This. Parent =parent; Position= -1;//The array element subscript starts at 0, and initially the default current cursor is set to-1, which is before the first element, #3 } Public BOOLMoveNext () {if(Position! = parent.values.Length)//determines whether the current position is the last, if not the cursor self-increment #4{Position++; } returnPosition < parent.values.Length; } Public ObjectCurrent {Get { if(Position = =-1|| Position = = Parent.values.Length)//the first and last post-access illegal #5 { Throw NewInvalidOperationException (); } Int32 Index= position + Parent.startingpoint;//consider the situation where the custom start location #6index = index%parent.values.Length; returnParent.values[index]; } } Public voidReset () {position= -1;//Resets the cursor to-1 #7 }}
To implement a simple iterator, you need to write so much code manually: you need to record the original collection of iterations # #, record the current cursor position # #, and return the element, set the position of the iterator in the array based on the current cursor and the starting position defined by the group. When initializing, the current position is set before the first element, # # #, and when the first call to the iterator needs to call MoveNext first, and then call the existing property. The condition of the current position is determined at the time of the cursor increment # #, so that no element can be returned if the first call to MoveNext is not an error. When we reset the iterator, we restore the position of the current cursor before the first element, # #. The code above is straightforward, except that it is error-prone to return the correct value in combination with the current cursor position and the custom starting position. Now, just return the IterationSample GetEnumerator iteration class that we wrote in the method of the class:
Public IEnumerator GetEnumerator () { returnnew iterationsampleenumerator ( this );}
It is important to note that the above is just a relatively simple example, there is not too much state to track, not to check whether the collection has changed during the iteration. In order to implement a simple iterator, we implemented so much code in c#1. It is convenient to use foreach when we implement a collection of IEnumerable interfaces using the framework's own, but we need to write so much code when we write our own collections to implement iterations. In C#1, it takes about 40 lines of code to implement a simple iterator, and now look at C#2 's improvements to the process.
2. C#2: Simplifying iterations with yield statements2.1 Introducing Iteration blocks (iterator) and yield return statements
C#2 makes iterations easier-reducing the amount of code also makes the code more elegant. The following code shows the complete code for implementing the GetEnumerator method in the c#2:
Public IEnumerator GetEnumerator () { for (int.0this. values. Length; index++) { yieldreturn values[(index + startingpoint)% values. Length]; }}
a few lines of code can fully implement the functionality required by the Iterationsampleiterator class. The method looks normal, except that yield return is used. This statement tells the compiler that this is not an ordinary method, but rather an iterative block that needs to be executed ( yield block ), which returns a IEnumerator object, and you can use an iteration block to perform an iterative method and return a The type that IEnumerable needs to implement, IEnumerator , or the corresponding generic type. If a non-generic version of an interface is implemented, the yield type of the iteration block is the Object type, otherwise the corresponding generic type is returned. For example, if a method implements the Ienumerable<string> interface, the type returned by yield is the string type. The normal return statement is not allowed in the iteration block except for yield return . All yield return statements in a block must return a type that is compatible with the last return type of the block. For example, if the method definition needs to return Ienumeratble<string> type, you cannot yield return 1. One thing to emphasize is that, for iterative blocks, although the method we write looks like it was executed sequentially, we actually let the compiler create a state machine for us. This is the part of the code that we write in c#1---the caller needs to return only one value at a time, so we need to remember the position in the collection when we last returned the value. When the compiler encounters an iteration block, it creates an inner class that implements the state machine. This class remembers the exact current position of our iterators as well as local variables, including parameters. This class is a bit like the code we wrote earlier, and he saves all the states that need to be recorded as instance variables. Here's a look at what the state machine needs to do sequentially in order to implement an iterator:
- It requires some initial state;
- when MoveNext is called, he needs to execute
GetEnumerator the code in the method to prepare the next data to be returned;
- when calling
Current property is, you need to return yielded the value;
- Need to know when the iteration ends Yes,
MoveNext will returnfalse。
2.2 Execution flow of iterators
The following code shows the execution flow of the iterator, and the code output (0,1,2,-1) is then terminated.
classProgram {Static ReadOnlyString Padding =NewString (' ', -); StaticIenumerable<int32>createenumerable () {Console.WriteLine ("{0} createenumerable () method starts", Padding); for(inti =0; I <3; i++) {Console.WriteLine ("{0} start yield {1}", i); yield returni; Console.WriteLine ("{0}yield End", Padding); } Console.WriteLine ("{0} yielding last value", Padding); yield return-1; Console.WriteLine ("{0} createenumerable () method End", Padding); } Static voidMain (string[] args) {IEnumerable<int32> iterable =createenumerable (); IEnumerator<int32> iterator =iterable. GetEnumerator (); Console.WriteLine ("Start Iteration"); while(true) {Console.WriteLine ("Call the MoveNext method ..."); Boolean result=iterator. MoveNext (); Console.WriteLine ("{0} returned by the MoveNext method", result); if(!result) { Break; } Console.WriteLine ("get current Value ..."); Console.WriteLine ("gets the current value of {0}", iterator. Current); } console.readkey (); }}
From the output, you can see the points:
- The
MoveNext CreateEnumerable method is not called until the first call.
- At
MoveNext the time of invocation, all operations have been done, and the return Current property does not execute any code.
- The code
yield return then stops executing and waits for the next time MoveNext the method is called to continue execution.
- You can have more than one statement in a method
yield return .
- After the last
yield return execution completes, the code is not terminated. The call MoveNext returns false to cause the method to end.
1th is especially important: this means that you cannot write any code in the iteration block that needs to be executed immediately at the time of the method invocation-for example, parameter validation. If you put the parameter validation in the iteration block, then he will not be able to work well, which is often the error place, and this error is not easy to find. Here's how to stop the iteration and finally the special execution of the statement block.
2.3 Special execution flow of iterators
In a normal method, a return statement usually has two functions, one returning the result of the caller's execution. The second is to terminate the execution of the method and execute the finally method in the statement before terminating. In the example above, we see that the yield return statement just briefly exits the method and MoveNext continues execution when it is called again. Here we do not write a finally block of statements. How to really exit the method when the statement block executes when exiting the method finnally , let's look at a simpler structure: the yield break statement block. end an iteration with yield break
StaticIenumerable<int32>countwithtimelimit (DateTime limit) {Try { for(inti =1; I <= -; i++) { if(DateTime.Now >=limit) { yield Break; } yield returni; } } finally{Console.WriteLine ("Stop the iteration! "); Console.readkey (); }}Static voidMain (string[] args) {DateTime Stop= DateTime.Now.AddSeconds (2); foreach(Int32 IinchCountwithtimelimit (stop)) {Console.WriteLine ("return {0}", i); Thread.Sleep ( -); }}
Reprinted from: http://www.yamatamain.com/article/21/1.html
Detailed C # iterator [go]