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进行遍历。

 

 

c#不要求实现IEnumerable/IEnumerable来使用foreach迭代数据类型。相反,编译器使用一个称为duck typing的概念;它查找GetEnumerator方法,该方法返回具有Current属性和MoveNext方法的类型。Duck typing涉及到按名称搜索,而不是依赖于对该方法的接口或显式方法调用。(“鸭子类型”一词源自将像鸭子一样的鸟视为鸭子的怪诞想法,对象必须仅实现 Quack 方法,无需实现 IDuck 接口。) 如果鸭子类型找不到实现的合适可枚举模式,编译器便会检查集合是否实现接口。

.NET 本质论 - 了解 C# foreach 的内部工作原理和使用 yield 的自定义迭代器

 

posted @ 2021-09-20 12:48  小林野夫  阅读(496)  评论(0编辑  收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/