Recommendation 17: Loop traversal with foreach in most cases
Since this recommendation involves the traversal of a collection, let's consider how to traverse the binding before starting this recommendation. Suppose there is an array whose traversal pattern can be traversed by the index, and if there is a Hashtable, the traversal pattern may be traversed by the key value. Regardless of which collection, if their traversal does not have a common interface, then the client, when traversing, is equivalent to encoding the specific type. In this way, our code must be modified when the requirements change. Moreover, because the client code pays too much attention to the implementation within the collection, the portability of the code becomes very poor, which directly violates the object-oriented open and closed principle. As a result, the iterator pattern is born. Now, instead of how to implement the pattern in the FCL, let's implement one of our own iterator patterns first.
// <summary> ///requires all iterators to implement this interface/// </summary> InterfaceImyenumerator {BOOLMoveNext (); ObjectCurrent {Get; } } /// <summary> ///requires all collections to implement the interface///in this way, the client can encode the interface,///without having to focus on specific implementations/// </summary> Interfaceimyenumerable {imyenumerator GetEnumerator (); intCount {Get; } } classmylist:imyenumerable {Object[] items =New Object[Ten]; Imyenumerator Myenumerator; Public Object This[inti] {Get{returnitems[i];} Set{ This. items[i] =value;} } Public intCount {Get{returnitems. Length; } } Publicimyenumerator GetEnumerator () {if(Myenumerator = =NULL) {Myenumerator=NewMyenumerator ( This); } returnMyenumerator; } } classMyenumerator:imyenumerator {intindex =0; MyList MyList; Publicmyenumerator (MyList MyList) { This. myList =myList; } Public BOOLMoveNext () {if(Index +1>mylist.count) {index=1; return false; } Else{Index++; return true; } } Public ObjectCurrent {Get{returnMylist[index-1]; } } }
Static voidMain (string[] args) { //use interface imyenumerable instead of MyListImyenumerable list =NewMyList (); //gets the iterator that encodes the iterator in the loop instead of the collection MyListImyenumerator Enumerator =list. GetEnumerator (); for(inti =0; I < list. Count; i++) { ObjectCurrent =Enumerator. Current; Enumerator. MoveNext (); } while(Enumerator. MoveNext ()) {ObjectCurrent =Enumerator. Current; } }
MyList simulates a collection class that inherits the interface imyenumerable, so that when the client invokes it, we can call imyenumerable directly to declare the variable, such as a statement in the code:
Imyenumerable list=new MyList ();
If we add another collection class in the future, then the encoding for list will work well even if you do not modify it. The GetEnumerator method is declared in Imyenumerable to return an object that inherits Imyenumerator. In MyList, the default return myenumerator,myenumerator is an implementation of the iterator, and if the requirements for the iteration change, you can re-develop an iterator (as shown below) and then use that iterator when the client iterates.
//use interface imyenumerable instead of MyListImyenumerable list =NewMyList (); //gets the iterator that encodes the iterator in the loop instead of the collection MyListImyenumerator Enumerator2 = New myenumerator (list);
//Forcall for(inti =0; I < list. Count; i++) { ObjectCurrent =Enumerator2. Current; Enumerator. MoveNext (); }
//while call while(Enumerator. MoveNext ()) {ObjectCurrent =Enumerator2. Current; }
In the client's code, we demonstrated the For loop and the while loop during the iteration, and by that time, because of the use of iterators, two loops were not encoded for mylist, but for the iterators.
Understanding the iterator pattern that you have implemented is equivalent to understanding the corresponding pattern provided in the FCL. In the above code, the interface and type are added to the word "My", in fact, the FCL has the corresponding interface and type, but in order to demonstrate the need to add some of the content, but the general idea is the same. The client-side code is written using the corresponding type in the FCL, which should generally look like this:
icollection<object > List = new
list<object > (); IEnumerator Enumerator = list. GetEnumerator ();
for (int i = 0 ; I < list. Count; I++ object current = enumerator. Current; Enumerator. MoveNext ();
while (enumerator. MoveNext ()) { object current = Enumerator. Current; }
However, both the For loop and the while loop are a bit verbose, so foreach appears.
foreach (var in list) { // omitted Object current = Enumerator. Current; }
As you can see, the use of foreach simplifies the code to a minimum. It is used to traverse a collection element that inherits the IEnumerable or Ienumerable<t> interface. With IL code, we look at what's going on with foreach:
. Method Private HidebysigStaticvoidMain (string[] args)CIL managed{ . EntryPoint //code size (0x3e) . Maxstack 2 . Locals Init([0]class[mscorlib] System.Collections.Generic.ICollection '1<Object> List, [1]ObjectCurrent , [2]class[mscorlib] System.Collections.Generic.IEnumerator '1<Object> cs$5$0000, [3]BOOLcs$4$0001) il_0000: NOP il_0001: newobj instance void class[mscorlib] System.Collections.Generic.List '1<Object>::.ctor ()il_0006: stloc.0 il_0007: NOP il_0008: ldloc.0 il_0009: callvirt instance class[mscorlib] System.Collections.Generic.IEnumerator '1<!0>class[mscorlib] System.Collections.Generic.IEnumerable '1<Object>::getenumerator ()il_000e: Stloc.2. try {il_000f: BR.Sil_001ail_0011: Ldloc.2 il_0012: callvirt instance!0 class[mscorlib] System.Collections.Generic.IEnumerator '1<Object>::get_current ()il_0017: stloc.1 il_0018: NOP il_0019: NOP il_001a: Ldloc.2 il_001b: callvirt instance BOOL[Mscorlib]system.collections.ienumerator::movenext ()il_0020: stloc.3 il_0021: Ldloc.3 il_0022: BRTRUE.Sil_0011il_0024: Leave. S il_0036}//end. Tryfinally {il_0026: Ldloc.2 il_0027: ldnull il_0028:ceqil_002a: stloc.3 il_002b: Ldloc.3 il_002c: BRTRUE.Sil_0035il_002e: Ldloc.2 il_002f: callvirt instance void[mscorlib]system.idisposable::D ispose ()il_0034: NOP il_0035: endfinally } //End Handler il_0036: NOP il_0037: Pager Int32[Mscorlib]system.console::read ()il_003c: Pop il_003d: ret} //end of Method Program::main
Viewing the IL code shows that the get_current () and MoveNext () methods are still called by the runtime.
After the MoveNext () method is called, if the result is true, jump to the beginning of the loop. In fact, the Foreach loop and the while loop are the same:
while (Enumerator. MoveNext ()) { object current = Enumerator. Current; }
In addition to providing a simplified syntax, the Foreach Loop has two additional advantages:
- Automatically place code into a try finally block
- If the type implements the IDisposable interface, it automatically calls the Dispose method after the loop ends.
Turn from: 157 recommendations for writing high-quality code to improve C # programs Minjia
157 recommendations for writing high-quality code to improve C # programs--recommendation 17: Loop traversal with foreach in most cases