C # iterator,

Source: Internet
Author: User

C # iterator [transfer],

The iterator pattern is an example of the behavioral pattern in the design pattern. It is a mode that simplifies communication between objects and is also a mode that is easy to understand and use. In simple terms, the iterator mode enables you to obtain all elements in a sequence without worrying about its type: array, list, linked list, or other sequence structures. This makes it very efficient to build a data pipeline-that is, data can enter the processing channel, perform a series of transformations, or filter, and then get the results. As a matter of fact, this is the core pattern of LINQ.

In. NET, the iterator mode is encapsulated by IEnumerator, IEnumerable, and their corresponding generic interfaces. If a class implements the IEnumerable interface, it can be iterated. Calling the GetEnumerator method will return the implementation of the IEnumerator interface, which is the iterator itself. The iterator is similar to a cursor in a database. It is a location record in a data sequence. The iterator can only move forward. Multiple iterators in the same data sequence can operate on the data at the same time.

The support for the iterator has been built in C #1, that is, the foreach statement. This allows for more direct and simple iteration of the set than for loop statements. The compiler will compile foreach to call the GetEnumerator, MoveNext method, and Current attribute. If the object implements the IDisposable interface, after the iteration is completed, the iterator is released. However, in C #1, implementing an iterator is relatively cumbersome. C #2 makes this work much simpler, saving a lot of effort to implement the iterator.

Next, let's look at how to implement an iterator and how C #2 simplifies the implementation of the iterator, and then list several examples of the iterator in real life.

1. C #1: tedious manual implementation of the iterator

Suppose we need to implement a new set type based on the ring buffer. We will implement the IEnumerable interface so that users can easily take advantage of all the elements in the set. Our remaining details focus on how to implement the iterator. The Set stores the values in the array. The set can set the start point of the iteration. For example, if the set has five elements and you can set the start point to 2, the iteration output is 2, 3, 4, 0, last 1. to make a simple presentation, we provide a constructor for setting values and starting points. This allows us to traverse the set in the following way:

object[] values = { "a", "b", "c", "d", "e" };IterationSample collection = new IterationSample(values, 3);foreach (object x in collection){    Console.WriteLine(x);}

Since we set the starting point to 3, the output result of the set is d, e, a, B, and c. Now, let's look at how to implement the IterationSample class iterator:

class IterationSample : IEnumerable{    Object[] values;    Int32 startingPoint;    public IterationSample(Object[] values, Int32 startingPoint)    {        this.values = 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? First, we need to save the current state of the cursor somewhere. On the one hand, the iterator mode does not return all data at a time, but the client requests only one data at a time. This means that we need to record the record in which the customer's current request is sent to the set. C #2 the compiler has done a lot of work for us to save the iterator state. Now let's take a look at the States to be saved and where they exist. Imagine we try to save the states in the IterationSample set so that it implements the IEnumerator and IEnumerable methods. At first glance, it seems possible that, after all, the data is in the correct place, including the starting position. Our GetEnumerator method only returns this. However, this method has a very important issue. If the GetEnumerator method is called multiple times, multiple independent iterators will return. For example, we can use two nested foreach statements to obtain all possible value pairs. These two iterations must be independent of each other. This means that the two iterator objects returned each time GetEnumerator is called must be independent. We can still implement the function directly in the IterationSample class. However, our class has multiple responsibilities, and this class carries a single responsibility principle. Therefore, we will create another class to implement the iterator itself. We use the internal class in C # To implement this logic. The Code is as follows:

Class IterationSampleEnumerator: IEnumerator {IterationSample parent; // iteration object #1 Int32 position; // current cursor position #2 internal IterationSampleEnumerator (IterationSample parent) {this. parent = parent; position =-1; // array element subscript starts from 0. The default current cursor is-1, that is, before the first element, #3} public bool MoveNext () {if (position! = Parent. values. length) // determine whether the current position is the last one. If it is not the auto-increment of the cursor #4 {position ++;} return position & lt; parent. values. length;} public object Current {get {if (position =-1 | position = parent. values. length) // The access from the first and last user is invalid #5 {throw new InvalidOperationException ();} Int32 index = position + parent. startingPoint; // when you want to customize the start position #6 index = index % parent. values. length; return parent. values [index] ;}} public void Reset () {position =-1; // Reset the cursor to-1 #7 }}

To implement a simple iterator, You need to manually write so much code: You need to record the original set #1 of the iteration and record the current cursor position #2. When an element is returned, set the iterator position #6 in the array based on the current cursor and the starting position defined in the array. During initialization, the Current position is set before the first element #3. When the iterator is called for the first time, MoveNext needs to be called before the Current attribute is called. When auto-increment cursor is used, the current position is judged by the condition #4, so that no element can be returned even when MoveNext is called for the first time. When resetting the iterator, we restore the current cursor to #7 before the first element. In addition to combining the current cursor position and the custom start position to return the correct value, the above code is very intuitive. Now, you only needIterationSampleClassGetEnumeratorThe method returns the iteration class we just wrote:

public IEnumerator GetEnumerator(){    return new IterationSampleEnumerator(this);}

It is worth noting that the above is just a relatively simple example. There are not many States to be tracked, so you don't have to check whether the set has changed during iteration. To implement a simple iterator, we have implemented so much code in C #1. We can use foreach to easily implement the set of IEnumerable interfaces provided by the Framework. However, when we write our own set to implement iteration, We need to write so much code. In C #1, about 40 lines of code is required to implement a simple iterator. Now let's look at the improvements made by C #2 to this process.

2. C #2: Use yield statements to simplify iteration 2.1 introduce iteration blocks (iterator) and yield return statements

C #2 makes iteration easier-reduces the amount of code and makes the code more elegant. The following code demonstrates the complete code for implementing the GetEnumerator method in C #2:

public IEnumerator GetEnumerator(){    for (int index = 0; index < this.values.Length; index++)    {        yield return values[(index + startingPoint) % values.Length];    }}

 

Complete implementation with just a few lines of codeIterationSampleIteratorFunctions required by the class. The method looks very common, except foryield return. This statement tells the compiler that this is not a common method, but an iteration block to be executed (yield block), He returnsIEnumeratorObject, you can use the iteration block to execute the iteration method and returnIEnumerableType to be implemented,IEnumeratorOr the corresponding generic type. If the implementation of the non-generic version interface, iteration block return yieldtypeYesObjectType. Otherwise, the returned type is the corresponding generic type. For exampleIEnumerable<string>Interface, thenyieldThe return type is String type. Besidesyield returnNormallyreturnStatement. Allyield returnThe statement must return a type compatible with the final return type of the block. For example, if the method definition needs to returnIEnumeratble<string>Type.yield return1. It should be emphasized that, for iterative blocks, although the methods we write seem to be executed in sequence, we actually let the compiler create a state machine for us. This is the sub-code we wrote in C #1. The caller only needs to return one value for each call. Therefore, we need to remember the position in the set when the last return value is returned. When the compiler encounters an iteration block, it creates an internal class that implements the state machine. This class remembers the exact current position of our iterator and local variables, including parameters. This class is a bit similar to the code we wrote previously. It saves all the States that need to be recorded as instance variables. Next let's take a look at the operations that need to be executed in order to implement an iterator:

  • It requires some initial states;
  • When MoveNext is called, it needs to executeGetEnumeratorTo prepare the next data to be returned;
  • WhenCurrentThe property is. You need to returnyieldedValue;
  • You need to know when the iteration ends,MoveNextWill returnfalse。
2.2 execution process of the iterator

The following code shows the execution process of the iterator. The code output (0, 1, 2,-1) is terminated.

Class Program {static readonly String Padding = new String ('', 30); static IEnumerable <int32> CreateEnumerable () {Console. writeLine ("{0} CreateEnumerable () method start", Padding); for (int I = 0; I & lt; 3; I ++) {Console. writeLine ("{0} start yield {1}", I); yield return I; Console. writeLine ("{0} yield ended", Padding);} Console. writeLine ("{0} Yielding last value", Padding); yield return-1; Console. writeLine ("{0} Cr EateEnumerable () method ends ", Padding);} static void Main (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 (" the obtained Current value is {0} ", iterator. Current);} Console. ReadKey ();}}

The output result shows the following points:

  • Until the first callMoveNext,CreateEnumerableIs called.
  • Before callingMoveNextWhen all operations are completedCurrentProperty does not execute any code.
  • Code inyield returnThen stop the execution and wait for the next call.MoveNextMethod.
  • There can be multipleyield returnStatement.
  • In the lastyield returnAfter the execution is complete, the Code is not terminated. CallMoveNextReturns false to end the method.

This means that you cannot write any code that needs to be executed immediately in the iteration block, for example, parameter verification. If you put the parameter verification in the iteration block, it will not work very well, which is often caused by errors, and this error is not easy to find. The following describes how to stop iteration andfinallyThe Special execution method of the statement block.

2.3 Special execution process of the iterator

In a common method,returnStatements usually have two functions: one is to return the invocation result of the caller. Second, the execution of the termination method should be executed before termination.finallyStatement. In the above example, we can see thatyield returnThe statement is just a short exit method.MoveNextRun the command again. We did not writefinallyStatement block. How to exit the method?finnallyThe following describes how to execute a statement block:yield breakStatement block.End an iteration with yield break

Static IEnumerable <int32> CountWithTimeLimit (DateTime limit) {try {for (int I = 1; I & lt ;= 100; I ++) {if (DateTime. now> = limit) {yield break;} yield return I ;}} finally {Console. writeLine ("Stop iteration! "); Console. readKey () ;}} static void Main (string [] args) {DateTime stop = DateTime. now. addSeconds (2); foreach (Int32 I in CountWithTimeLimit (stop) {Console. writeLine ("Return {0}", I); Thread. sleep (300 );}}

 

Reprinted from: http://www.yamatamain.com/article/21/1.html

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.