Analysis on the Improvement and Implementation Principles of Iterators in C # 2.0

zhaozj2021-02-17  65

http://flier_lu.blogone.net/?id=1511638

The C # language draws a very practical Foreach statement from VB. For all instances of all classes that support the IENUMERABLE interface, the Foreach statement uses a unified interface to traverse its child, so that the cumbersome meter of the previously long FOR cycle is completely automatically completed by the compiler. A class that supports the IENUMERABLE interface is usually implemented with an internal IENUMERATOR interface and passing the IEnumerable.GeteNumerator function, allowing the user to complete traversal work such as a Foreach statement. This feature is very convenient, but it is necessary to pay a certain price. Juval Lowy Posted in MSDN Magazine 2004 No. 5 of Create Elegant Code with Anonymous Methods, Iterators, And Partial Classes, in detail, in detail, introduces itself support and other new features in C # 2.0. First, because the ienumerator.current attribute is a value of an Object type, the value type (VALUE TYPE) collection is in the foreach statement, each value must experience a useless Box and UNBOX operations; even if it is a reference type (Reference Type) The collection, when used by the foreach statement, there is also a redundant CastClass instruction to ensure the correctness of the enumerated value for type conversion.

The following is quoted:

Using system.collections;

Public Class tokens: ienumerable

{

...

Tokens f = new tokens (...);

FOREACH (String Item in F)

{

Console.writeLine (item);

}

...

}

The simple code above is automatically converted to

The following is quoted:

Tokens f = new tokens (...);

IENUMERATOR ENUM = f.Getenumerator ();

Try

{

Do {

String item = (string) enum.get_current (); // Redundancy Conversion

Console.writeLine (item);

WHILE (enum.movenext ());

}

Finally

{

If (Enum is idisposable) / / Need to verify that the class that implements the IENUMERATOR interface is supported by idisposable interface

{

(IDisposable) ENUM .dispose ();

}

}

Fortunately, the concept of generic is supported in C # 2.0, providing a strong type of generic version IENUMERABLE definition, the pseudo code is as follows:

The following is quoted:

Namespace system.collections.generic

{

Public Interface IEnumerable

{

IEnumerator getenumerator ();

}

Public Interface IEnumerator : idisposable

{

ItemType Current {Get;}

Bool movenext ();

}

}

In this way, the type of safety is safe when traversing the collection, but also can operate directly to the actual type of collections, avoid redundant conversion, and improve efficiency.

The following is quoted:

Using system.collections.Generic;

Public Class tokens: ienumerable

{

... // Realize IEnumerable Interface Tokens F = New Tokens (...);

FOREACH (String Item in F)

{

Console.writeLine (item);

}

}

The above code is automatically converted to

The following is quoted:

Tokens f = new tokens (...);

IEnumerator Enum = f.GeteNumerator ();

Try

{

Do {

String item = enum.get_current (); // No conversion

Console.writeLine (item);

WHILE (enum.movenext ());

}

Finally

{

If (enum) / / No need to verify that the class that implements the IENUMERATOR interface supports the IDisposable interface,

// Because all IENUMERATOR interfaces automatically generated by the compiler are supported

{

(IDisposable) ENUM .dispose ();

}

}

In addition to the redundant redundancy reducing performance over time, the other unhappy thing in the C # existing version is that IENUMERATOR interface is too much trouble. It is usually implemented by an in-line IENUMERATOR interface, and in addition to the GET_CURRENT () function, the functions of other parts are basically the same, such as

The following is quoted:

Public Class tokens: ienumerable

{

PUBLIC STRING [] Elements;

Tokens (String Source, Char [] DELIMITERS)

{

// Parse the string into tokens:

Elements = Source.Split (Delimiters);

}

Public ienumerator geteNumerator ()

{

Return New tokenenumerator (this);

}

// Inner Class Implements IENUMERATOR Interface:

Private class tokenenumerator: ienumerator

{

PRIVATE INT POSITION = -1;

Private tokens T;

Public Tokenenumenumerator (Tokens T)

{

THIS.T = T;

}

// Declare The Movenext Method Required by IEnumerator:

Public bool movenext ()

{

IF (position

{

POSITION ;

Return True;

}

Else

{

Return False;

}

}

// Declare the reset method required by ienumerator:

Public void reset ()

{

Position = -1;

}

// Declare The Current Property Required by ienumerator:

Public Object Current

{

Get // get_current function

{

Return T.Elements [Position];

}

}

}

...

}

The Position and Tokens of the inline TOKENUMERATORALATOR are actually a class of each implementation IENUMERATOR interface, just the GET function of the Current property. This aspect C # 2.0 has made great improvements, adding the Yield keyword support, allowing code logical reuse. The length of the above code is only a few lines in C # 2.0, as follows:

Using system.collections.Generic;

Public Class tokens: ienumerable

{

Public IEnumerator getNumerator ()

{

For (int i = 0; i

Yield Elements [i];

}

...

}

The GetEnumerator function is a C # 2.0 support iteration block (Iterator Block), telling the compiler through Yield to return what value is returned, and then the compiler automatically completes the IENUMERATOR Interface Mint work. Yield Break statement supports the direct end of iterative blocks, such as

The following is quoted:

Public IEnumerator getenumerator ()

{

For (int i = 1; i <5; i )

{

Yield Return I;

IF (i> 2)

Yield Break; // i> End of traversal

}

}

In this way, it is easy to implement the IEnumerator interface, and it is convenient to support a variety of enumeration methods in a class, such as

The following is quoted:

Public Class CityCollection

{

String [] m_cities = {"New York", "Paris", "London"}

Public IEnumerable Reverse

{

get

{

For (int i = m_cities.length-1; i> = 0; I -)

Yield m_cities [i];

}

}

}

Next, let's see where the compiler has done for us behind the language characteristics. Take the TOKENS class that supports the IENUMERABLE interface as an example, the code of the GetEnumerator function is packaged by the compiler, the pseudo code is as follows.

The following is quoted:

Public Class tokens: ienumerable

{

Private Sealed Class GetENUMERATOR $ 00000000__ienumeratoriMPL

: IEnumerator , IEnumerator, IDisposable

{

Private int $ pc = 0;

PRIVATE STRING $ _CURRENT;

Private tokens ;

Public INT I $ 00000001 = 0;

// Implement IEnumerator Interface

String ienumerator .Get_current ()

{

Return $ _CURRENT;

}

Bool IEnumerator .MOVENEXT ()

{

Switch ($ PC)

{

Case 0:

{

$ PC = -1;

I $ 00000001 = 0;

Break;

}

Case 1:

{

$ PC = -1;

I $ 00000001 ;

Break;

}

DEFAULT:

{

Return False;

}

}

IF (i $ 00000001 < .Elements.Length)

{

$ _CURRENT = .Elements [i $ 00000001];

$ PC = 1;

Return True;

}

Else

{

Return False;

}

}

// Implement the Ienumerator interface

Void IEnumerator.reset ()

{

Throw new Exception ();

}

String ienumerator.get_current ()

{

Return $ _CURRENT;

}

Bool ienumerator.movenext ()

{

Return IEnumerator .MOVENEXT (); // Call IEnumerator Interface Implementation

}

/ / Implement IDisposable interface

void dispose ()

{

}

}

Public IEnumerator getNumerator ()

{

GetENUMERATOR $ 00000000__ienumerator 00000000__ienumerator 00000000__ienumerator 00000000__ienumerator 00000000__ienumerator 00000000__ienumeratoriMPL ();

IMPL. = this;

Return IMPL;

}

}

From the pseudo code we can see, the C # 2.0 compiler actually maintains a very similar internal class with the TokenEnumerator class that implements the IEnumerator interface in front to encapsulate the implementation of the IENUMERATOR interface. The implementation logic of this inline is determined according to the Yield return location defined by the GetENUMERATOR. Let's take a look at the implementation of a more complex iterative block, support recursive iterations, the code is as follows:

The following is quoted:

Using system;

Using system.collections.Generic;

Class Node

{

Public Node leftNode;

Public Node RightNode;

Public t item;

}

Public Class BinaryTree

{

Node M_ROOT;

Public void add (params t [] items)

{

Foreach (T Item in Items)

Add (item);

}

Public Void Add (T Item)

{

// ...

}

Public IEnumerable inorder

{

get

{

Return scaninorder (m_root);

}

}

Ienumerable Scaninorder (Node root)

{

IF (root.leftNode! = null)

{

Foreach (T Item in Scaninorder (Root.leftNode)

{

Yield item;

}

}

Yield root.Item;

Root.rightNode! = NULL)

{

Foreach (T Item in Scaninorder (Root.rightNode))

{

Yield item;

}

}

}

}

BinaryTree provides an inorder property that supports the IEnumerable interface, traversing the entire binary tree via the Scaninorder function. Because IENUMERABLE interface is not the class itself, it is an attribute, so the compiler first generates an inline support IENUMERABLE interface. The pseudo code is as follows

The following is quoted:

Public Class BinaryTree

{

Private Sealed Class ScanIrder $ 00000000__ienumeratorImpl

: Ienumerator , ienumerator, idisposable

{

BinaryTree ;

Node root;

// ...

}

PRIVATE Sealed Class ScanIrder $ 000000__ienumerableImpl

: Ienumerable , IENUMERABLE

{

BinaryTree ;

Node root;

IEnumerator ienumerable .getenumerator ()

{

Scaninorder $ 00000000__ienumeratoriMPL Impl = New Scaninorder $ 00000000__ienumeratorImpl ();

iPL. = this. ;

Impl.Root = this.root;

Return IMPL;

}

IEnumerator ienumerable.getenumerator ()

{

Scaninorder $ 00000000__ienumeratoriMPL Impl = New Scaninorder $ 00000000__ienumeratorImpl ();

iPL. = this. ;

Impl.Root = this.root;

Return IMPL;

}

}

Ienumerable Scaninorder (Node root)

{

Scaninorder $ 00000000__ienumerableImpl IMPL = New Scaninorder $ 000000__ienumerableImpl ();

IMPL. = this;

Impl.root = root;

Return IMPL;

}

}

Because the Scaninorder function content needs to use the root parameters, the package class of the IEnumerable and IEnumerator interface needs to have a root field, saving the parameters of the incoming Scaninorder function, and passed to the final implementation function. Implementing the Ienumerator Interface Springs $ 00000000__ienumeratoriMPL Implementation principle is roughly the same as the previous example, and the program logic is highly complicated, and the iDisposable interface is required to complete the resource recycling.

The following is quoted:

Public Class BinaryTree

{

Private Sealed Class GetENUMERATOR $ 00000000__ienumeratoriMPL: IEnumerator , IEnumerator, IDisposable

{

Private int $ pc = 0;

PRIVATE STRING $ _CURRENT;

Private tokens ;

Public INT I $ 00000001 = 0;

Public IEnumerator __Wrap $ 00000003;

Public IEnumerator __Wrap $ 00000004;

Public T Item $ 00000001;

Public T Item $ 00000002;

Public Node root;

// Implement IEnumerator interface

String IEnumerator .GET_CURRENT ()

{

Return $ _CURRENT;

}

Bool IEnumerator .MOVENEXT ()

{

Switch ($ PC)

{

Case 0:

{

$ PC = -1;

IF (root.leftNode! = null)

{

__wrap $ 00000003 = .scaninorder (root.leftNode) .getenumerator ();

Goto scanleft;

}

Else

{

Goto GetItem;

}

}

Case 1:

{

Return False;

}

Case 2:

{

Goto scanleft;

}

Case 3:

{

$ PC = -1;

Root.rightNode! = NULL)

{

__Wrap $ 00000004 = .scaninorder (root.rightnode) .geetenumerator ();

Goto scanright;

}

Else

{

Return False;

}

Break;

}

Case 4:

{

Return False;

}

Case 5:

{

Goto scanright;

}

DEFAULT:

{

Return False;

}

Scanleft:

$ PC = 1;

IF (__ wrap $ 00000003.MOVENEXT ())

{

$ _CURRENT = Item $ 00000001 = __Wrap $ 00000003.Get_current ();

$ PC = 2;

Return True;

}

GetItem:

$ PC = -1;

IF (__ wrap $ 00000003! = NULL)

{

(Idisposable) __ wrap $ 00000003) .dispose ();

}

$ _CURRENT = root.item;

$ PC = 3;

Return True;

Scanright:

$ PC = 4;

IF (__ wrap $ 00000004.MOVENEXT ())

{

$ _CURRENT = $ 00000002 = __wrap $ 00000004.get_current ();

$ PC = 5;

Return True;

}

Else

{

$ PC = -1;

IF (__ wrap $ 00000004! = NULL)

{

(Idisposable) __ wrap $ 00000004) .dispose ();

}

Return False;

}

}

/ / Implement IDisposable interface

void dispose ()

{

Switch ($ PC)

{

Case 1:

Case 2:

{

$ PC = -1;

IF (__ wrap $ 00000003! = NULL)

{

(Idisposable) __ wrap $ 00000003) .dispose ();

}

Break;

}

Case 4:

Case 5:

{

$ PC = -1;

IF (__ wrap $ 00000004! = NULL)

{

(Idisposable) __ wrap $ 00000004) .dispose ();

}

Break;

}

}

}

}

}

Through the top of the pseudo code, we can see that C # 2.0 is actually a recursive segment that is completed by a limited state machine for the argument of $ PC, which may be because the limited state machine can easily generate it through the program. . The Dispose () function is responsible for processing the intermediate variable of the state machine. Interested in further understanding of the iterative features, you can read the ITERATORS related articles on the BLOG of Grant Ri. After understanding the implementation principle of Iterators, then those discussions will not be confused by their appearances: D

转载请注明原文地址:https://www.9cbs.com/read-29264.html

New Post(0)