C # iterator

Source: Internet
Author: User
Tags mscorlib

C # iterator
In. NET, the iterator mode is implemented through the IEnumerator and IEnumerable interfaces and their generic versions. If a class implements the IEnumerable interface, it means that it can be accessed by iteration. Calling the GetEnumerator () method will return the implementation of IEnumerator, which is the iterator itself. In C #1.0, the foreach statement is used to implement built-in support for the access iterator, making the traversal of the set simple and clear. In fact, foreach is implemented by calling the GetEnumerator, MoveNext method, and Current attribute. Therefore, to obtain the iterator in C #1.0, you must implement the GetEnumerator method in the IEnumerable interface, to implement an iterator, we need to implement the syntax sugar provided by the MoveNext and Reset methods in the IEnumerator interface in C #2.0 to simplify the implementation of the iterator, the yield keyword can be used to simplify the implementation of the iterator. The iterator implementation in C #1.0 assumes that we want to implement a character list type and can traverse this type through foreach. In C #1.0, you must implement the IEnumerable and IEnumerator interfaces. Copy the code namespace IteratorTest {class Program {static void Main (string [] args) {CharList charList = new CharList ("Hello World"); foreach (var c in charList) {Console. writeLine (c);} Console. read () ;}} class CharList: IEnumerable {public string TargetStr {get; set;} public CharList (string str) {this. targetStr = str;} public IEnumerator GetEnumerator () {return new CharIterator (this. targetStr) ;}} Class CharIterator: IEnumerator {// reference the public string TargetStr {get; set ;}// specifies the position of the current traversal public int position {get; set ;} public CharIterator (string targetStr) {this. targetStr = targetStr; this. position = this. targetStr. length;} public object Current {get {if (this. position =-1 | this. position = this. targetStr. length) {throw new InvalidOperationException ();} return this. ta RgetStr [this. position] ;}} public bool MoveNext () {// if the condition for continued traversal is met, set the position value if (this. position! =-1) {this. position --;} return this. position>-1;} public void Reset () {this. position = this. targetStr. length ;}}in the preceding example, CharIterator is the implementation of the iterator. The position field stores the Current iteration position and obtains the elements of the Current iteration position through the Current attribute, the MoveNext method is used to update the iteration position and check whether the next iteration position is valid. When we debug the following statement in a single step through VS, The foreach (var c in charList) code first runs to the charList of the foreach statement to obtain the CharIterator instance of the iterator, after the code is executed in, the MoveNext method of the iterator is called. Finally, the variable c gets the value of the Current attribute of the iterator. After the previous step is completed, a new loop is started, call the MoveNext method to obtain the value of the Current attribute. C #2.0 use yield to simplify the iterator implementation through the code of the iterator in C #1.0. To implement an iterator, you must implement the IEnumerator interface, then implement the MoveNext, Reset method, and Current attributes in the IEnumerator interface. In C #2.0, yield statements can be directly used to simplify the implementation of the iterator. Class CharList: IEnumerable {public string TargetStr {get; set;} public CharList (string str) {this. targetStr = str;} public IEnumerator GetEnumerator () {for (int index = this. targetStr. length; index> 0; index --) {yield return this. targetStr [index-1] ;}} the code above shows that the entire CharIterator class can be replaced by the yield return statement. The yield return statement tells the compiler to implement an iterator block. If the return type of the GetEnumerator method is not a generic interface, the yield type of the iterator block is an object. Otherwise, it is a type parameter of the generic interface. The IL Code shows that for the yield return Statement, the compiler generates a nested type for US (nested type) <GetEnumerator> d _ 0, this class implements the IEnumerator interface. 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 written in C #1.0, which saves all the States to be recorded as instance variables. To implement an iterator, this state machine needs to perform operations in sequence: it must have an initial state when MoveNext is called, he needs to execute the code in the GetEnumerator method to prepare the next data to be returned. When the Current attribute is called, it must return the previous generated data and know when the iteration ends. MoveNext will return false. Note, to avoid packing and unpacking in the iterator, we need to implement the generic version of The iterator. Because the generic IEnumerable <T> interface inherits the generic IEnumerable interface, we need to add System to the generic iterator code. collections. IEnumerator System. collections. IEnumerable. getEnumerator () {return GetEnumerator ();} in this way, the non-generic method calls the generic method instead of implementing the non-generic IEnumerable interface. The previous section briefly describes the workflow of the iterator. The following is an example of the workflow of the iterator. Class Program {static readonly String Padding = new String ('', 30); static IEnumerable <Int32> CreateEnumerable () {Console. writeLine ("{0} Start of CreateEnumerable", Padding); for (int I = 0; I <3; I ++) {Console. writeLine ("{0} About to yield {1}", Padding, I); yield return I; Console. writeLine ("{0} After yield", Padding);} Console. writeLine ("{0} Yielding final value", Padding); yield return-1; Co Nsole. writeLine ("{0} End of CreateEnumerable ()", Padding);} static void Main (string [] args) {IEnumerable <Int32> iterable = CreateEnumerable (); IEnumerator <Int32> iterator = iterable. getEnumerator (); Console. writeLine ("Starting to iterate"); while (true) {Console. writeLine ("Calling MoveNext ()... "); Boolean result = iterator. moveNext (); Console. writeLine ("... moveNext result = {0} ", result); if (! Result) {break;} Console. writeLine ("Fetching Current... "); Console. writeLine ("... current result = {0} ", iterator. current);} Console. read () ;}} generally, the iterator combines the foreach statement, and foreach calls the Dispose method at the end. For demonstration, the while statement is used in the code to implement a loop. Interrupt a little and insert a content Introduction. Generally, to implement IEnumerable, we only return IEnumerator. If we only generate a sequence in the method, we can return IEnumerable. Therefore, you can change the code to the following method: static IEnumerator <Int32> CreateEnumerable (){......} ...... // IEnumerable <Int32> iterable = CreateEnumerable (); IEnumerator <Int32> iterator = CreateEnumerable (); The IL code of the two methods is different, here, only the built-in types of the compiler are listed to implement those interfaces. For more details, refer to ILSpy: IEnumerable is returned. class nested private auto ansi sealed beforefieldinit '<CreateEnumerable> d _ 0' extends [mscorlib] System. object implements class [mscorlib] System. collections. generic. IEnumerable '1 <int32>, [mscorlib] System. collections. IEnumerable, cl Ass [mscorlib] System. Collections. Generic. IEnumerator '1 <int32>, [mscorlib] System. Collections. IEnumerator, [mscorlib] System. IDisposable {......} Returns IEnumerator. class nested private auto ansi sealed beforefieldinit '<CreateEnumerable> d _ 0' extends [mscorlib] System. object implements class [mscorlib] System. collections. generic. IEnumerator '1 <int32>, [mscorlib] System. collections. IEnumerator, [mscorlib] System. IDisposable {......} Back to this example, the output result of the program is: There are several notes in this Code: it is not until the method in MoveNext and CreateEnumerable is called for the first time. When MoveNext is called, all operations have been completed, and the Current attribute is not executed. No code is executed after yield return, in the next call to the MoveNext method, multiple yield return statement codes may not end at the last yield return Statement in different places where the same method continues to be executed, instead, it is particularly important to end the execution of a method by returning the MoveNext call with false: this means that if you need to execute the method immediately when calling the method, you cannot use the iterator block. For example, if you put the parameter verification in the iteration block, it will not work very well, which is often caused by errors, and such errors are not easy to find. To learn more about the iterator workflow, the return statement usually has two functions in the conventional method: one is to return the invocation result of the caller. The second is to terminate the execution of the method. Execute the method in the finally statement before the termination. In the above example, we can see that the yield return statement only exits the method for a short time and continues to be executed when MoveNext is called. The finally code block is not checked at all. How can I actually exit? How does the finnally statement block execute the exit method? Let's take a look at a simple structure: yield break statement block. Using yield return to end iterator execution usually has only one exit point, but sometimes we want to "exit early. For iterator blocks, yield break can achieve the desired effect. It can terminate the iteration immediately, so that false is returned for the next call to MoveNext. The following code demonstrates the iteration from 1 to 100, but stops when the time expires: class Program {static IEnumerable <Int32> CountWithTimeLimit (DateTime limit) {for (int I = 1; I <= 100; I ++) {if (DateTime. now> = limit) {yield break;} yield return I ;}} static void Main (string [] args) {DateTime stop = DateTime. now. addSeconds (2); foreach (Int32 I in CountWithTimeLimit (stop) {Console. writeLine ("Received {0}", I); Thread. sleep (300);} Consol E. writeLine ("End of Main"); Console. read () ;}} from the program output, we can see that the yield break statement behavior is similar to the return of a common method. iteration of the iterator is stopped and exited early. Next let's take a look at how and when the finally statement block is executed. In general, the finally statement block is executed when the method execution exits from a specific area. The finally statement block in the iteration block is different from the finally statement block in the normal method. As we can see, the yield return Statement stops method execution rather than exiting the method. According to this logic, in this case, the statements in the finally statement block are not executed. However, when the yield break statement is run, the finally statement block is executed. Generally, the finally statement is used to release resources in the iteration block, just like the using statement. The following example shows how to execute the finally statement. The finally statement is always executed, whether it is 100 iterations, or the iteration is stopped because of the time, or an exception is thrown. Static IEnumerable <Int32> CountWithTimeLimit (DateTime limit) {try {for (int I = 1; I <= 100; I ++) {if (DateTime. now> = limit) {yield break;} yield return I ;}} finally {Console. writeLine ("Stopping") ;}} is executed only when the statement in the iteration block after MoveNext is called. If MoveNext is not used, if MoveNext is called several times and then the call is stopped, what will happen? See the following code: DateTime stop = DateTime. now. addSeconds (2); foreach (Int32 I in CountWithTimeLimit (stop) {if (I> 3) {Console. writeLine ("Returning"); return;} Thread. sleep (300);} in the above Code, we do not stop the execution iterator block in advance, but stop using the iterator in advance. After the return statement in the foreach loop is executed, the finally code of the iterator is also executed. Finally is executed because foreach calls the Dispose method provided by IEnumerator in its finall code block. Before the iterator completes the iteration, if you call the Dispose method on the iterator created by the iterator code block, then the state machine executes any finally code block within the range of the current "paused" position of the Code. This is a bit complicated, but the result is easy to explain: as long as the foreach loop is used, the finally block in the iteration block will be executed as expected. The preceding description can be verified using the following code: static void Main (string [] args) {DateTime stop = DateTime. now. addSeconds (2); IEnumerable <Int32> iterable = CountWithTimeLimit (stop); IEnumerator <Int32> iterator = iterable. getEnumerator (); iterator. moveNext (); Console. writeLine ("Reveived {0}", iterator. current); iterator. moveNext (); Console. writeLine ("Reveived {0}", iterator. current); // display the finally code block that calls Dispose to execute the iterator // iterator. D Ispose (); Console. writeLine ("End of Main"); Console. read () ;}summary this article shows how to implement an iterator in C #1.0 and how to simplify the implementation of an iterator through yield return provided in C #2.0. Through the introduction of the iterator workflow, we can see the delayed execution of yield return. The yield return statement only indicates the "temporary" Exit method.

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.