foreach内部做的一点事
1 在C#中,foreach的使用简化了很多循环语法的书写。如果我们仅仅把foreach当成for循环的省略写法的话,就显得有点大才小用了。 2 3 事实上,foreach与“迭代”和“枚举”密切相关。 4 5 C#编译器会把foreach语句转换为IEnumerable接口的方法和属性。 6 7 例如: 8 9 foreach (var p in persons) 10 11 { 12 13 Console.WriteLine(p); 14 15 } 16 17 以上代码迭代persons数组中的所有元素,并逐个显示他们。 18 19 foreach语句会解析成下面的代码。首先调用GetEnumerator()方法,获得数组的一个枚举。 20 21 在while循环中(只要MoveNext()返回true),用Current属性访问数组中的元素: 22 23 IEnumerator enumerator = persons.GetEnumerator(); 24 25 while (enumerator.MoveNext()) 26 27 { 28 29 Person p = (Person) enumerator.Current; Console.WriteLine(p); 30 31 } 32 33 这里要说明的是。用[]声明数组是C#中使用Array类的记号。Array类实现了IEnumerable接口中的GetEnumerator()方法。 34 35 所以使用foreach语句迭代数组,其实是使用了Array类中个GetEnumerator()方法。 36 37 也就是说,只要是实现了IEnumerable接口中的GetEnumerator()方法的类,都可以用foreach语句来迭代。 38 39 IEnumerable接口中的GetEnumerator()方法是这样定义的: 40 41 IEnumerator GetEnumerator() 42 43 其返回的类型是一个IEnumerator接口。 44 45 IEnumerator接口中的Current属性返回光标所在的元素。 46 47 IEnumerator接口中的MoveNext()方法移动到集合的下一个元素上,如果有这个元素,该方法就返回true。如果集合不再有更多的元素,该方法就返回false。 48 49 IEnumerator接口中的Reset()方法将光标重新定位于集合的开头。许多枚举会抛出NotSupportedException异常。 50 51 下面,我们来写一个实现了IEnumerable接口的类。 52 53 public class HelloCollection:IEnumerable 54 55 { 56 57 public IEnumerator GetEnumerator() 58 59 { 60 61 yield return "Hello"; 62 63 yield return "World"; 64 65 } 66 67 } 68 69 现在可以用foreach语句迭代了: 70 71 static void Main(string[] args) 72 73 { 74 75 HelloCollection helloCollection=new HelloCollection (); 76 77 foreach (string s in helloCollection ) 78 79 { 80 81 Console.WriteLine(s); 82 83 } 84 85 } 86 87 实际上,yield return语句返回集合的一个元素,并移动到下一个元素上。它会将类HelloCollection解析成如下代码: 88 89 public class HelloCollection : IEnumerable 90 91 { 92 93 public IEnumerator GetEnumerator() 94 95 { 96 97 Enumertor enumerator = new Enumerator(); 98 99 return enumerator; 100 101 } 102 103 public class Enumertor : IEnumerator, IDisposable 104 105 { 106 107 private int state; 108 109 private object current; 110 111 public Enumertor(int state) 112 113 { 114 115 this.state = state; 116 117 } 118 119 } 120 121 bool System.Collections .IEnumerator .MoveNext() 122 123 { 124 125 switch (state) 126 127 { 128 129 case 0: 130 131 current = "Hello"; 132 133 state = 1; 134 135 return true; 136 137 case 1: 138 139 current = "World"; 140 141 state = 2; 142 143 return true ; 144 145 case 2: 146 147 break ; 148 149 } 150 151 return false ; 152 153 } 154 155 void System.Collections .IEnumerator .Reset() 156 157 { 158 159 throw new NotSupportedException(); 160 161 } 162 163 object System.Collections .IEnumerator .Current 164 165 { 166 167 get 168 169 { 170 171 return current; 172 173 } 174 175 } 176 177 void IDisposable.Dispose() 178 179 { 180 181 } 182 183 } 184 185 186 187 188 189 190 191 foreach语句默认用GetEnumerator()方法迭代,也可以自行制定迭代方法。举例: 192 193 public class MusicTitles:IEnumerable 194 195 { 196 197 string[] names = { "Tubular Bells", "Hergest Ridge", "Ommadawn", "Platinum" }; 198 199 public IEnumerator GetEnumerator() /*顺序迭代*/ 200 201 { 202 203 for (int i = 0; i < 4; i++) 204 205 yield return names[i]; 206 207 } 208 209 public IEnumerator Reverse() /*逆序迭代*/ 210 211 { 212 213 for (int i = 3; i >= 0; i--) 214 215 yield return names[i]; 216 217 } 218 219 } 220 221 在foreach语句中不必写明使用GetEnumerator()方法迭代,因为这是默认方法。如下: 222 223 static void Main(string[] args) 224 225 { 226 227 MusicTitles titles = new MusicTitles(); 228 229 foreach (string title in titles) 230 231 { 232 233 Console.WriteLine(title); 234 235 } 236 237 Console.WriteLine(); 238 239 foreach (string title in titles.Reverse()) 240 241 { 242 243 Console.WriteLine(title); 244 245 } 246 247 }