浅析foreach原理
在日常开发工作中,我们发现很多对象都能通过foreach来遍历,比如HashTable、Dictionary、数组等数据类型。那为何这些对象能通过foreach来遍历呢?如果写一个普通的Person类,也希望它能通过foreach来遍历应该怎么做?通过查看,发现HashTable、Dictionary、数组等数据类型都实现了一个叫IEnumerable(或其泛型版本)的接口。现在也来尝试下,让Person类实现这个接口(其实实不实现IEnumerable接口不是必须的,只要类型中有public IEnumerator GetEnumerator()这个方法即可):
1 class Person:IEnumerable 2 { 3 public string[] _Name = new string[] { "sk", "jk", "yzk","wcw","ml" }; 4 public string Name { get; set; } 5 public int Age { get; set; } 6 7 public IEnumerator GetEnumerator() 8 { 9 return new PersonEnumerator(_Name); 10 } 11 }
可以看到GetEnumerator()方法需要一个返回值类型为实现了IEnumerator的类型,那就写个类,让其实现IEnumerator接口:
1 class PersonEnumerator:IEnumerator 2 { 3 public PersonEnumerator(string[] name) 4 { 5 this._names = name; 6 } 7 8 public string[] _names { get; set; } 9 public int Index = -1; 10 11 public object Current 12 { 13 get { return _names[Index]; } 14 } 15 16 public bool MoveNext() 17 { 18 Index++; 19 if (Index>=_names.Length) 20 { 21 return false; 22 } 23 else 24 { 25 return true; 26 } 27 } 28 29 public void Reset() 30 { 31 Index = -1; 32 } 33 }
此时再运行程序,就发现Person类可以遍历了。运行结果如下:
总结,一种类型要想通过foreach遍历,其内部必须有public IEnumerator GetEnumerator()这个方法,而通常的做法是让类型实现IEnumerable接口。
上面的代码还有一个问题,就是Person对象通过foreach遍历时,var并没有推断出是string类型而是object类型,这是因为Current就是object导致的,解决的方案就是泛型,看下面的代码:
1 class Person 2 { 3 public string Name { get; set; } 4 public int Age { get; set; } 5 public string[] _Name = new string[] { "zxh", "jk", "ml", "wcw", "sk", "yzk","lmn" }; 6 7 public IEnumerator<string> GetEnumerator() 8 { 9 return new MyClass<string>(_Name); 10 } 11 } 12 13 class MyClass<T>:IEnumerator<T> 14 { 15 public MyClass(T[] _Names) 16 { 17 this.names = _Names; 18 } 19 public T Current 20 { 21 get { return names[Index]; } 22 } 23 24 private T[] names { get; set; } 25 private int Index = -1; 26 27 public void Dispose() 28 { 29 //throw new NotImplementedException(); 30 } 31 32 object IEnumerator.Current 33 { 34 get { throw new NotImplementedException(); } 35 } 36 37 public bool MoveNext() 38 { 39 Index++; 40 if (Index>=names.Length) 41 { 42 return false; 43 } 44 else 45 { 46 return true; 47 } 48 } 49 50 public void Reset() 51 { 52 Index = -1; 53 } 54 }
使用的方法如下:
1 Person p1 = new Person(); 2 foreach (var item in p1) 3 { 4 Console.WriteLine(item); 5 }
可以看到var已经被推断成string.
下面看一下,自己写的foreach遍历,还是那个Person类(必须有IEnumerator GetEnumerator()这个方法):
把foreach遍历换成下面的代码:
1 Person p1 = new Person(); 2 IEnumerator enumer = p1.GetEnumerator(); 3 while (enumer.MoveNext()) 4 { 5 Console.WriteLine(enumer.Current); 6 }
上面的代码也是能够正常执行的。
扩展:
1 List<string> strLst = new List<string>(); //查看定义得知,其实现了IEnumerable<T>和IEnumerable2个接口 2 strLst.AddRange(new string[] { "sk", "jk", "yzk" }); 3 IEnumerator enumer = strLst.GetEnumerator(); 4 while (enumer.MoveNext()) 5 { 6 Console.WriteLine(enumer.Current); 7 }
一般没人会这么写,遍历一个对象还是直接写foreach。这只不过是其原理。
1 Dictionary<int, char> dic = new Dictionary<int, char>(); 2 dic.Add(1, '壹'); 3 dic.Add(2, '贰'); 4 dic.Add(3, '叁'); 5 dic.Add(4, '肆'); 6 dic.Add(5, '伍'); 7 dic.Add(6, '陆'); 8 dic.Add(7,'柒'); 9 dic.Add(8,'扒'); 10 dic.Add(9,'玖'); 11 12 IEnumerator iEnumer = dic.GetEnumerator(); 13 while (iEnumer.MoveNext()) 14 { 15 KeyValuePair<int, char> item = (KeyValuePair<int, char>)iEnumer.Current; 16 Console.WriteLine(item.Key+" "+item.Value); 17 }