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 }