. Net Programming Interface Analysis Series iterator zz

Source: Internet
Author: User
In the previous article, we talked about the icomparable and icomparer interfaces. In this article, I will tell you about the two interfaces ienumerator and ienumerable used for enumeration element sets. Ienumerator is used to implement an iterator (equivalent to the iterator mentioned in the previous C ++ Article). It has some methods and attributes required to list all elements in a data structure. The ienumerable interface is used to return an iterator object. A type that implements ienumerable indicates that this type of object can be enumerated.
Let's take a look at the definitions of these two interfaces!

IEnumerator

The iterator is used to enumerate all elements in a data structure.

 namespace System.Collections 
{
[ComVisible(true)]
[Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")]
public interface IEnumerable
{
[DispId(-4)]
IEnumerator GetEnumerator();
}
}

From the above definition, we can see that an emurator has the most basic capabilities to enumerate all elements in a data structure:
Ability to get the current element: Current attribute;
Ability to move element pointers: movenext method;
Ability to reset the iterator: reset method.
The current attribute is of the object type, that is, all types of elements can be returned, and the corresponding generic interface is: system. collections. generic. ienumerator <t>, which not only inherits ienumerator, but also adds a specific type of current attribute.
Ienumerable
Ienumerable declares an enumerative type, and its definition is simple, that is, returning an iterator.

namespace System.Collections 
{
[ComVisible(true)]
[Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")]

We should be familiar with the C # traversal syntax, that is, the foreach statement. When talking about foreach, it actually exists in C ++, but it is implemented in the library in the form of functions. For C #, it has been implemented in language. In most cases, we should try to use the foreach statement to traverse a set object instead of writing a for loop or another while loop. The reason is simple: efficiency. The foreach syntax needs to implement the IEnumerable interface for the enumerated object type.
The Generic interface corresponding to IEnumerable is: System. Collections. Generic. IEnumerable <T>.Design a collection class

Generally, IEnumerator and IEnumerable are used together. Suppose we design a Data Structure class MyCollection of our own and let it be enumerated. How should we design it as a whole? Let's take a look at the following code.

Class MyCollection: IEnumerable
{
Public struct MyEmurator: IEnumerator
{
// The implementation code is omitted here
}
// Some implementation code is omitted here
Public IEnumerator GetEnumerator ()
{
Return new MyEmurator (this );
}
}

This is a typical application method for IEnumerator and IEnumerable. Almost all containers in System. Collection are set in this way. Implement IEnumerable for the container type itself, indicating that the container can be enumerated. The iterator type is a nested type. The Container class interface function GetEnumerator is used to return the iterator instance. Generally, a container is closely related to its iterator, and a container with an iterator is enough to define the iterator as a nested type, avoiding management confusion.

Implement a 2D List type

The two-dimensional List type we are talking about here is actually to implement a List with the List as the element. In short, this List2D is a List used to store the List; but when we enumerate, instead of enumerating the list in List2D, you want to enumerate the elements in the list directly.
Here we define a generic List2D class <T> and implement the IEnumerable <T> interface. To streamline the code, List2D is not allowed to implement the IList interface, and only a few simple methods such as Add and Clear are provided. Let's talk about the following code!

Class List2D <T>: IEnumerable <T>
{
// Nested iterator type
Public struct Emurator: IEnumerator <T>
{
// Do not write the code here
}
Private List <T> _ lists = new List <T> (); // storage List

Public List <T> this [int index]
{
Get {return _ lists [index];}
}

Public int Count
{
Get
{
Int count = 0;
Foreach (List <T> list in _ lists)
{
Count + = list. Count;
}
Return count;
}
}

Public void Add (List <T> item)
{
_ Lists. Add (item );
}

Public void Clear ()
{
_ Lists. Clear ();
}

# Region IEnumerable Members

Public IEnumerator GetEnumerator ()
{
Return (IEnumerable <T>) this). GetEnumerator ();
}
# Endregion

# Region IEnumerable <T> Members

IEnumerator <T> IEnumerable <T>. GetEnumerator ()
{
Return new Emurator (this );
}
# Endregion
}
Let's take a look at the definition of struct emurator of the iterator type, which is nested in list2d and implements the ienumerator <t> interface. For implementation of the iterator, see the notes.
Class List2D <T>: IEnumerable <T>
{
Public struct Emurator: IEnumerator <T>
{
List2D <T> _ list2D; // The enumerated 2D list
IEnumerator <List <T> _ listsEmuretor; // List iterator
IEnumerator <T> _ listEmuretor; // element iterator
Bool _ started; // whether to start Enumeration
T _ current; // The current element.

Public Emurator (List2D <T> list2D)
{
_ List2D = list2D;
_ ListsEmuretor = list2D. _ lists. GetEnumerator ();
_ ListEmuretor = default (IEnumerator <T> );
_ Started = false;
_ Current = default (T );
}

# Region IEnumerator Members

Public object Current
{
Get {return _ current ;}
}

Public bool MoveNext ()
{
If (! _ Started) // The first MoveNext. The first list is required.
{
_ Started = true;
If (! _ ListsEmuretor. MoveNext ())
Return false;

_ ListEmuretor = _ listsEmuretor. Current. GetEnumerator (); // gets the iterator of the first list
}

While (true)
{
If (! _ ListEmuretor. MoveNext ())
{
// The enumeration of the current list ends. You need to move it to a list.
If (! _ ListsEmuretor. MoveNext ())
Return false; // if all lists are traversed, false is returned.

_ ListEmuretor = _ listsEmuretor. Current. GetEnumerator ();
}
Else // there are still elements in the current list. Successful
{
_ Current = _ listEmuretor. Current;
Return true;
}
}
}

Public void Reset ()
{
_ ListsEmuretor. Reset ();
_ Current = default (T );
_ Started = false;
}
# Endregion

# Region IEnumerator <T> Members
T IEnumerator <T>. Current
{
Get {return _ current ;}
}
# Endregion

Public void Dispose ()
{
}
}
}

It's really not easy. After writing some code, we can implement this 2D List iterator. We will write some test code in the Main function to see if it can run normally.

Static void Main (string [] args)
{
List2D <string> list2D = new List2D <string> (); // list of 2-dimensional strings

List <string> list1 = new List <string> ();
List1.Add ("list1-1 ");
List1.Add ("list1-2 ");
List1.add ("list1-3 ");
List2d. Add (list1); // The first list has three elements.

List <string> list2 = new list <string> ();
List2d. Add (list2); // The second list has no elements

List <string> list3 = new list <string> ();
List1.add ("list3-1 ");
List1.add ("list3-2 ");
List2d. Add (list3); // The third list has two elements.

Foreach (string STR in list2d) // enumerate all strings
{
Console. writeline (STR );
}

Console. readkey ();

The running result is as follows.
List1-1
List1-2
List1-3
List3-1
List3-2
We can see that the running results are completely correct.Yield Return

From the above we may find that writing an iterator may be quite troublesome, but the demand may be quite simple, however, we need to write a lot of code to implement an iterator. Fortunately, C # provides us with a dedicated tool: yield return! With yield return, we can implement the getenumerator function without writing an iterator.
Yield return is different from normal function return. In terms of function, it is equivalent to returning an enumerated element every time yield return is called. The yield return syntax can only be used in functions whose return value type is ienumerator. Let's take a look at how the list2d getenumerator function is implemented using yield return, so that we don't need to create a custom emurator type.

 IEnumerator<T> IEnumerable<T>.GetEnumerator() 
{
foreach (List<T> list in _lists)
{
foreach (T item in list)
{
yield return item;
}
}
}

7 rows! Four curly arc nodes use only seven lines of code to implement all the functions of an iterator. To implement an iterator, we wrote dozens of lines of code, this is a big improvement in coding efficiency. Of course, not all situations are suitable for yield return, but it is the best choice under general requirements.
People who are used to traditional programming languages may be more difficult to understand the yield return, and may be more confused about how it is implemented. In fact, yield return is a syntax provided by C # At the language level to simplify the code, in fact, the C # compiler helped us complete Part of the coding work-it automatically created an anonymous iterator type for us, in addition, the iterator is used to implement the iteration function that we implement using the yield return semantics.
If you are skeptical about the program, you can use the ildasm.exe tool provided by vs.netto view the EXE file compiled with the yield return code. Ildasm.exe (vs2005) is usually stored in the "C: \ Program Files \ Microsoft Visual Studio 8 \ SDK \ V2.0 \ bin \" directory.
From the ildasm.exe below, we can see that there is a nested type GetEnumerator> d _ 0 <T> In the List2D type, and this is exactly the anonymous iterator type that the C # compiler generates for us.

When sighing the intelligence of C # compiler, we need to have a deeper understanding of the implementation of yield return, that is, how the code of the automatically generated iterator is, what is the difference between the iterator and the iterator we write ourselves. You can use ildasm to view the IL code, but this is a very painful thing. Even if you are familiar with all the commands of IL, it is also a very painful thing. So here I want to recommend a powerful tool for code learning ,. net anti-compiler ". net Reflector ", which can be compiled.. Net Program decompiling into C # code, and has a high restoration rate, and the tool itself is still being upgraded. You can download this software from http://www.aisto.com/roeder/dotnetfor free.
Yield Return can be used anywhere in the GetEnumerator function. Our program structure generally includes the sequential structure, loop, and Branch selection. To understand the details of the compiler's processing of yield return, we first design a simple class, which only uses yield return to first return a-1, then returns a 100, and then ends, this is a simple, but sequential execution structure. The code for this class is as follows:

class TestYieldReturn:IEnumerable 
{
public IEnumerator GetEnumerator()
{
yield return -1;
yield return 100;
}
}

We compiled this program and decompiled it with. NET Reflactor, so we got the code of the iterator automatically generated by the compiler:

[CompilerGenerated]
Private sealed class <GetEnumerator> d _ 0: IEnumerator <object>, IEnumerator, IDisposable
{
// Fields
Private int <> 1 _ state;
Private object <> 2 _ current;
Public TestYieldReturn <> 4 _ this;

// Methods
[DebuggerHidden]
Public <GetEnumerator> d _ 0 (int <> 1 _ state)
{
This. <> 1 _ state = <> 1 _ state;
}

Private bool MoveNext ()
{
Switch (this. <> 1 _ state)
{
Case 0:
This. <> 1 _ state =-1;
This. <> 2 _ current =-1; // first returns-1
This. <> 1 _ state = 1;
Return true;

Case 1:
This. <> 1 _ state =-1;
This. <> 2 _ current = 100; // then 100 is returned.
This. <> 1 _ state = 2;
Return true;

Case 2:
This. <> 1 _ state =-1; // The end is reached and the status is set to end.
Break;
}
Return false;
}

[DebuggerHidden]
Void IEnumerator. Reset ()
{
Throw new NotSupportedException ();
}

Void IDisposable. Dispose ()
{
}

// Properties
Object IEnumerator <object>. Current
{
[DebuggerHidden]
Get
{
Return this. <> 2 _ current;
}
}
Object IEnumerator. Current
{
[DebuggerHidden]
Get
{
Return this. <> 2 _ current;
}
}
}

We can see that there is an attribute before this iterator type: [compilergenerated], which indicates that this iterator is indeed generated by the compiler. In addition to the current and the enumerated object "<> 4 _ this", the member variable of the iterator also adds a member of "<> 1 _ state, this state is the state variable used to implement the sequence program structure. We can analyze the most important movenext function. The 0, 1, and 2 of state represent the first yield return, respectively, the state of the program after the first yield return and the second yield return, and-1 indicates that the enumeration ends. The compiler uses State member variables to control the order of sequential logic.
Similarly, the C # compiler sets some other auxiliary variables to implement control structures such as loops and branches. Sometimes these structures are combined, and the compiler can implement them well.Version Control

Let's take a look at this code. It wants to delete all negative numbers in the list.

List<int> list=new List<int>(); 
list.Add(0);
list.Add(-1);
list.Add(2);

foreach(int i in list)
{
if(i<0) list.Remove(i);
}

This Code seems to have been written, and compilation can also be passed. However, an exception will be thrown when running. It will throw an invalidoperationexception with the error message "collection was modified; enumeration operation may not execute .".
How can this happen? It turns out that when Microsoft defines the iterator function, it is required to ensure that the enumerated objects cannot add or remove elements in the process from the beginning to the end of enumeration. In fact, this requirement is also well understood. I believe that no one wants to count the number of people in the team, but the people in the team are still coming in and out, right?
Readers who are good at thinking here may immediately think of a problem. How does the iterator know that this set has changed? Here, Microsoft uses a simple and clever method to set a version information for the Collection object. It checks the version information continuously during the iteration process to determine whether the collection object has been updated. The procedure is as follows.
1. Add a private int _ version member to the Collection object and set the initial value to 0;
2. In all operations that directly update the Set elements, such as insert and remove, _ version ++ is used;
3. Enumerator also sets a _ version member and sets it as a _ version Member of the collection object in the constructor. In fact, the current version of the collection is recorded.
4. During the iteration process, Enumerator constantly checks the _ version field of the collection. Once it is found that it is different from the _ version value originally recorded, it is deemed that the collection has been updated and an exception is thrown immediately.

Some comments

From foreach to yield return and C #3. x's LINQ and so on, we found that C # is doing more and more things for us, but we do not know when this change will come to an end or when our learning will come to an end. C # introduce these new features to simplify our coding work, but at the same time it will inevitably bring about some new problems, that is, C # is becoming more and more huge and complex, one day, we may find that we don't know what C # is. It is not a good thing to put everything into the language layer. It feels good to have a new technology every day, but yesterday's new technology immediately becomes an old technology today, but it may be frustrating. We need to innovate, but more importantly, we need a classic. When will C # become a classic?

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.