Specification 4.1 iterator block
An iterator block (iterator blocks) is a block that produces an ordered sequence of values. The difference between an iterator block and a normal statement block is one or more yield statements that appear.
The yield return statement produces the next value of the iteration.
The yield break statement represents the completion of the iteration.
As long as the return value type of the corresponding function member is an enumerator interface (see 4.1.1) or an enumerable interface (see 4.1.2), an iterator block can be used as a method body, operator body, or accessor body.
An iterator block is not a stand-alone element in C # syntax. They are subject to a variety of factors and have a significant impact on the semantics of function member declarations, but they are only blocks in syntax.
When a function member is implemented with an iterator block, if a function member's form argument list specifies a ref or out parameter, a compilation error is caused.
If a return statement is present in an iterator block, the error is compiled (but the yield return statement is allowed).
If the iterator block contains an unsafe context, it can cause a compilation error. An iterator block must be defined in a secure context, even if its declaration is nested within an unsafe context.
4.1.1 Enumerator interface
The enumerator (enumerator) interface includes non-generic interface System.Collections.IEnumerator and generic interfaces system.collections.generic.ienumerator<t> Of all instantiations. In this chapter, these interfaces are called IEnumerator and ienumerator<t>, respectively.
4.1.2 an enumerable interface
The enumerable (enumerable) interfaces include Non-generic interface System.Collections.IEnumerable and generic interface system.collections.generic.ienumerable<t>. In this chapter, these interfaces are called IEnumerable and Ienumerable<t>, respectively.
4.1.3 Build Type
An iterator block can produce an ordered sequence of values in which all systems have the same type. This type is called the build type of the iterator block (yield type).
The build type of the iterator block that is used to implement a function member that returns IEnumerator or IEnumerable is object.
The generation type of the iterator block for the next function member returning ienumerator<t> or ienumerable<t> is T.
4.1.4 This access
In an iterator block in an instance member of a class, the expression this is a value. The type of this value is the class in which this usage occurs, and it is a reference to the object on which the invoked method is located.
In an iterator block in an instance member of a struct, the expression this is a variable. The type of this variable is the structure in which this usage occurs. This variable stores a copy of the structure of the invoked member. The this variable in an iterator block in an instance member of a struct is exactly the same as a value variable of the type of the struct.
4.2 Enumerator Object
If a function member uses an iterator block to return an enumerator interface type, the call to the function member does not immediately execute the code in the iterator block, but rather establishes and returns an enumerator object. This object encapsulates the code specified in the iterator block, while execution of the code specified in the iterator occurs when the MoveNext () method of the enumerator object is invoked. An enumerator object has the following characteristics:
It implements IEnumerator and Ienumerator<t>, where T is the generation type of the iterator block.
It realizes the System.IDisposable.
It is initialized with the value of the parameter passed to the function member (if any) and the instance value.
It has four possible states: Before, running, suspended, and after, whose initial state is before.
A typical enumerator object is an instance of an enumerator class that encapsulates the code in an iterator block and implements the enumerator interface, which is automatically generated by the compiler, but other implementations are allowed. If an enumerator class is generated automatically by the compiler, the class is nested directly or indirectly within the function member, has private accessibility, and has a name that is reserved by the compiler.
An enumerator object can implement an interface other than the one described above.
The following sections describe in detail the exact behavior of MoveNext (), Current, and Dispose () members in the IEnumerable and Ienumerable<t> interfaces implemented by an enumerator object.
Note that the enumerator object does not support the Ienumerator.reset () method. Calling this method throws a System.notsupporteexception exception.
4.2.1 MoveNext () method
The MoveNext () method of the enumerator object encapsulates the code for the iterator block. The call to the MoveNext () method executes the code in the iterator block and sets an appropriate value for the current property of the enumerator object. The exact action done by the MoveNext () method depends on the state that the calling MoveNext () method is the enumerator object:
If the state of the enumerator object is before, call the MoveNext () method:
Sets the status to running.
Initializes the arguments of the iterator block object, including this, to the value of the variable and the instance value that were saved when the enumerator object was initialized.
Executes from the beginning of the execution of the iterator block until it is interrupted (discussed below).
If the state of the enumerator object is running, the result of the call to the MoveNext () method is unspecified.
If the state of the enumerator object is suspended, call the MoveNext () method:
Sets the status to running.
Restores all local variables and parameters (including this) to the value that was saved when the iterator block was last suspended during execution. Note that the contents of the objects referenced by these variables may change after the last MoveNext ().
Continue execution of the iterator block from the yield return statement where the interrupt was last executed, until execution is interrupted again (discussed below).
If the state of the enumerator object is after, the call to the MoveNext () method returns FALSE.
When the MoveNext () method executes an iterator block, the execution is interrupted in four ways: The yield return statement, the yield break statement, the end of the iterator block encountered, and the exception thrown in the iterator block and propagated outside the block.
When you encounter the yield return statement (see 4.4):
The expression given in the statement is evaluated, implicitly converted to the build type, and assigned to the current property of the enumerator object.
Suspends the execution of the iterator body. Saves the value of all local variables and parameters (including this) and the location of the yield return statement. If the yield return statement is in one or more try blocks, the finally block associated with it is not executed at this time.
Sets the state of the enumerator object to suspended.
Returns true to the caller of the MoveNext () method, indicating that the iteration has been successfully transferred to the next value.
When the yield break statement (see 4.4) is encountered:
If the yield break statement is in one or more try blocks, execute the finally block associated with it.
Sets the state of the enumerator object to after.
Returns false to the caller of the MoveNext () method, representing the completion of the iteration.
When a fast end of the iterator is encountered:
Sets the state of the enumerator object to after.
Returns false to the caller of the MoveNext () method, representing the completion of the iteration.
When an iterator express an exception and propagates it outside the block:
The appropriate finally block in the iterator block is executed.
Sets the state of the enumerator object to after.
Propagates the exception to the caller of the MoveNext () method.
4.2.2 Current Property
The current property of an enumerator object is affected by the yield return statement in the iterator block.
When an enumerator object is in the suspended state, the value of the current property is set by the last call to the MoveNext () method. When an enumerator object is in the before, running, or after state, the result of accessing the current property is undefined.
If the build type of an iterator block is not object, the IEnumerable implemented through the enumerator object and the corresponding ienumerator<t> access to current will convert the result to object.
4.2.3 Dispose () method
The Dispose () method clears the iterator by setting the state of the enumerator object to after.
If the state of the enumerator object is before, call the Dispose () method to set its state to after.
If the state of the enumerator object is running, the result of calling the Dispose () method is undefined.
If the state of the enumerator object is suspended, call the Dispose () method:
Sets the status to running.
Executes all finally blocks, as if the yield return statement is a yield break statement. If this causes the exception to be thrown and propagated outside the iterator block, the state of the enumerator object is set to after and the exception is propagated to the caller of the Dispose () method.
Set the status to After.
Calling the Dispose () method has no effect if the state of the enumerator object is after.
4.3 Enumerable object
When a function member that returns an enumerable interface type uses an iterator block, a call to that function member does not immediately execute the code in the iterator block, but rather establishes and returns an enumerable object. The enumerable object has a GetEnumerator () method that can return an enumerator object. The enumerator object encapsulates the code specified in the iterator block, and executes the code in the iterator block when the MoveNext () method of this enumerator object is invoked. An enumerable object has the following characteristics:
It implements IEnumerable or Ienumerable<t>, where T is the generation type of the iterator block.
It is initialized with the value of the parameter passed to the function member (if any) and the instance value.
A typical enumerable object is an instance of an enumerable class that encapsulates the code in an iterator block and implements an enumerable interface that is automatically generated by the compiler, but other implementations are allowed. If an enumerable class is generated automatically by the compiler, the class is nested directly or indirectly within a function member, has private accessibility, and has a name that is reserved by the compiler.
An enumerable object can implement an interface other than the one above. For example, an enumerable object can also implement IEnumerator and Ienumerator<t>, making it both enumerable and an enumerator. In this case, when the GetEnumerator () method of an enumerable object is invoked for the first time, the enumerable object itself is returned. Subsequent calls to the GetEnumerator () method of an enumerable object (if any) will return a copy of the enumerable object. Therefore, each returned enumeration appliance has its own state, and an enumerator and other enumerators do not affect each other.
4.3.1 GetEnumerator () method
An enumerable object provides an implementation of the GetEnumerator () method of the IEnumerator and Ienumberator<t> interfaces. Two GetEnumerator () methods share an implementation that can fetch and return a valid enumerator object. The enumerator object initializes a parameter value and an instance value that is saved when the enumerable object is initialized, and the function of the enumerator object is described in section 4.2.
4.4 Yield Statement
The yield statement in an iterator block is used to generate a value, or to emit a signal that an iteration completes.
In order to ensure compatibility with existing programs, yield is not a reserved word, only when a return statement immediately followed, the yield statement has this special meaning. In other cases, the yield statement can be used as an identifier.
The yield statement appears with the first many restrictions, as described below:
If a yield statement appears outside the method body, operator body, or accessor body, it can cause a compilation error.
If a yield statement appears inside an anonymous method, a compilation error is caused.
If a yield statement appears in a finally or a try block, it causes a compilation error.
If a yield statement appears inside a try block with a catch statement, a compilation error is caused.
The following example shows the valid and invalid usage of some yield statements.
Delegate ienumerable<int> D ();
Ienumerator<int> GetEnumerator () {
try {
Yield return 1; That's right
Yield break; That's right
}
finally {
Yield return 2; Error, yield appears in finally block E
Yield break; Error, yield appears in finally block
}
try {
Yield return 3; Error, the yield return statement appears in the Try...catch statement
Yield break; That's right
}
catch {
Yield return 4; Error, the yield return statement appears in the Try...catch statement
Yield break; That's right
}
D d = Delegate {
Yield return 5; Error, yield statement appears in anonymous method
};
}
int MyMethod () {
Yield return 1; Error, iterator block has wrong return value type
}
There must be an implicit conversion from the type of the expression in the yield return statement to the build type of the iterator block (see 4.1.3).
The yield return statement is executed in accordance with the following steps:
The expression given in the statement is evaluated, implicitly converted to the build type, and then assigned to the current property of the enumerator object.
Suspends execution of the iterator block. If the yield return statement is in one or more try blocks, the corresponding finally block is temporarily not executed.
The MoveNext () method returns true to its caller, indicating that the enumerator object successfully advances to the next value.
The next call to the MoveNext () method of the enumerator object restores execution of the iterator block from the last pending place.
The yield break statement executes according to the following steps:
If the yield break statement is in one or more try blocks with a finally block, the control is transferred to the finally block corresponding to the innermost try block. When the control process encounters the end of the finally block (if it can), the control is transferred to the finally block corresponding to the outer layer of the try block. This process persists until the finally block corresponding to all try statements is executed.
Returns control to the caller of the iterator block. This may be returned from the MoveNext () method or the Dispose () method.
Because a yield break statement unconditionally transfers control to another place, the end point of a yield break will never be reached.
4.4.1 Definitely assigned value
For the following form of the yield return statement:
yield return expr;
For a variable V, the same definite assignment is made at the beginning of expr and at the beginning of the statement.
If a variable v is definitely assigned at the end of expr, it is definitely assigned at the end of the statement, otherwise it is not definitely assigned at the end of the statement.
4.5 Examples
This section describes the possible implementations of iterators in the standard C # structure. The implementation described here is based on the same principles as the Microsoft C # compiler, but is by no means the only possible implementation.
The following Stack<t> class uses an iterator to implement its GetEnumerator () method. The iterator enumerates all the elements in the stack in order from top to bottom.
Using System;
Using System.Collections;
Using System.Collections.Generic;
Class Stack<t>: ienumerable<t> {
t[] items;
int count;
public void Push (T item) {
if (items = null) {
Items = new T[4];
}
else if (items. Length = = count) {
t[] NewItems = new T[count * 2];
Array.copy (items, 0, NewItems, 0, Count);
Items = NewItems;
}
items[count++] = Item;
}
Public T Pop () {
T result = Items[--count];
Items[count] = T.default;
return result;
}
Public ienumerator<t> GetEnumerator () {
for (int i = count-1 i >= 0;-I.) yield items[i];
}
}
The GetEnumerator () method can be converted to an instance of an enumerator class that is automatically generated by the compiler, encapsulating the code specified in the iterator block as follows:
Class Stack<t>: ienumerable<t> {
...
Public ienumerator<t> GetEnumerator () {
Return to New __enumerator1 (this);
}
Class __enumerator1:ienumerator<t>, IEnumerator {
int __state;
T __current;
Stack<t> __this;
int i;
Public __enumerator1 (stack<t> __this) {
This.__this = __this;
}
Public T Current {
get {return __current;}
}
Object IEnumerator.Current {
get {return __current;}
}
public bool MoveNext () {
Switch (__state) {
Case 1:goto __state1;
Case 2:goto __state2;
}
i = __this.count-1;
__loop:
if (i < 0) goto __state2;
__current = __this.items[i];
__state = 1;
return true;
In the above transformation, the code in the iterator block is converted to a state machine and placed in the MoveNext () method of the enumerator class. In addition, the local variable i is converted to the domain of the enumerator object, so it will always exist during the call to the MoveNext () method.
The following example prints a simple multiplication table of integers 1 through 10. The FromTo () method in the example returns an enumerable object that is implemented with an iterator.
Using System;
Using System.Collections.Generic;
Class Test {
static ienumerable<int> FromTo (int from, int to) {
while (from <= to) yield return from++;
}
static void Main () {
Ienumerable<int> e = FromTo (1, 10);
foreach (int x in e) {
foreach (int y in e) {
Console.Write ("{0,3}", X * y);
}
Console.WriteLine ();
}
}
}
The FromTo () method can be converted to an instance of an enumerable class that is automatically generated by the compiler, encapsulating the code in the iterator block as follows:
Using System;
Using System.Threading;
Using System.Collections;
Using System.Collections.Generic;
Class Test {
...
static ienumerable<int> FromTo (int from, int to) {
Return the new __enumerable1 (from, to);
}
Class __enumerable1:ienumerable<int>, IEnumerable, Ienumerator<int>, IEnumerator {
int __state;
int __current;
int __from;
int from;
int to;
int i;
Public __enumerable1 (int __from, int to) {
This.__from = __from;
This.to = to;
}
Public ienumerator<int> GetEnumerator () {
__enumerable1 result = this;
if (Interlocked.compareexchange (ref __state, 1, 0)!= 0) {
result = new __enumerable1 (__from, to);
Result.__state = 1;
}
Result.from = Result.__from;
return result;
}
This enumerable class implements both an enumerable and an enumerator interface, so it is both enumerable and an enumerator. When the GetEnumerator () method is invoked for the first time, the enumerable object itself is returned. Subsequent calls to the GetEnumerator () method (if any) will return a copy of the enumerable object. So each enumerator that returns has its own state, and the change of one enumerator does not affect the other enumerators. The Interlocked.coompareexchange () method can be used to ensure thread safety.
The From and to parameters are converted to the domain of the enumerable class. Because the iterator block changed from, an additional __from field was introduced to hold the initial value of from in each enumerator.
When __state is 0 o'clock, the MoveNext () method runs out of a InvalidOperationException exception. This will ensure that the enumerable object is used directly as an enumerator without first invoking the GetEnumerator () method.
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.