C#foreach 本质( 鸭子类型遍历)
探讨关于C#中Foreach的本质
要实现foreach需要满足什么条件?
只要类中实现类中的GetEnumerator()方法、MoveNext()方法、Current属性(俗称鸭子类型)都可以使用foreach进行遍历。
所以只要继承IEnumerable或IEnumerator、集合类、数组 等类都可以使用foreach遍历。
c#不要求实现IEnumerable/IEnumerable来使用foreach迭代数据类型。相反,编译器使用一个称为duck typing的概念;它查找GetEnumerator方法,该方法返回具有Current属性和MoveNext方法的类型
以下案例可以看出:编译器用鸭子类型这一概念。该案例编译器不报错,但是运行时候会出错,运行时进行类型检测,发现cars 类无法转化成IEnumerator接口。
通过4种foreach的用法来了解真面模
List<int> i = new() { 1, 1, 1, 2 }; int[] j = { 1, 1, 1, 2 }; //用法一 foreach (var item in i) { // item = 22; 错误的写法 item就是 enumerator.Current属性,该属性是只读的; Console.Write(item); } //用法二 foreach (var item in j) { Console.Write(item); } //用法三 foreach (var item in new F()) { Console.Write(item + ", "); // 1, 2, 3, 4, 5, } class F { public IEnumerator<int> GetEnumerator() { for (var i = 0; i < 5; ++i) { yield return i; } } }
上面代码通过ILspy的反编译IIL代码如下:
具体分析:
1、数组是静态的直接可以通过索引确定。集合类是不确定长度的所以不能通过索引方式。数组继承了IEnumerable接口,该接口要求返回IEnumerator
对象。
2、集合类实现了IEnumerator接口
3、迭代返回的对象也现实了IEnumerator接口。
通过以上三种方式的使用可知foreach主要用到类中的GetEnumerator()方法、MoveNext()、Current属性。那么我们是否可以推断出不需要继承IEnumerator
接口,只要实现这三个方法的类照样可以使用foreach。
为了验证这个想法,我自定义一个类来验证,代码如下:
使用方法4:
using System.Collections; CarFactory cars = new CarFactory(); foreach (var car in cars) { Console.WriteLine(((Car)car).Modle); } public class CarFactory { private Car[] carlist; int position = -1; //Create internal array in constructor. public CarFactory() { carlist =new Car[2] { new Car { Modle = "长城" }, new Car { Modle = "红旗" }}; } public bool MoveNext() { position++; return (position < carlist.Length); } public CarFactory GetEnumerator() { return this; } public Car Current { get { return carlist[position]; } } } public class Car { public string Modle { get; set; } } /*输出: 长城 红旗*/
反编译后如下:
[CompilerGenerated] internal class Program { private static void <Main>$(string[] args) { CarFactory cars = new CarFactory(); CarFactory enumerator = cars.GetEnumerator(); try { while (enumerator.MoveNext()) { Car car = enumerator.Current; Console.WriteLine(car.Modle); } } finally { IDisposable disposable = enumerator as IDisposable; if (disposable != null) { disposable.Dispose(); } } }
案例5、应用扩展方法 注入方法GetEnumerator()
//扩展方法 由于CarFactory类中没有GetEnumerator()方法,只能用扩展方法注入。 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CExtention { static class CarsExtention { public static CarFactory GetEnumerator(this CarFactory carFactory) => carFactory; } } //主类 using System.Collections; using CExtention; CarFactory cars = new CarFactory(); foreach (var car in cars) { Console.WriteLine(((Car)car).Modle); } public class CarFactory { private Car[] carlist; int position = -1; //Create internal array in constructor. public CarFactory() { carlist =new Car[2] { new Car { Modle = "长城" }, new Car { Modle = "红旗" }}; } public bool MoveNext() { position++; return (position < carlist.Length); } public Car Current { get { return carlist[position]; } } } public class Car { public string Modle { get; set; } } /*输出: 长城 红旗*/
通过对以上使用方法的分析我们可以得出
Foreach 只要类中实现类中的GetEnumerator()方法、MoveNext()、Current属性。都可以使用foreach进行遍历。
总结:
.NET 本质论 - 了解 C# foreach 的内部工作原理和使用 yield 的自定义迭代器