小解c# foreach原理

简介: 小解c# foreach原理

【本篇文章首发于51CTO,https://developer.51cto.com/art/202010/628737.htm

作为开发人员我们经常会在程序中编写 foreach 语句实现对类型的遍历,但是并不是所有的类型都可以遍历,这个知识点是绝大部分开发成员所知晓的。但是类型可以被 foreach 遍历的依据是什么部分程序员并不清楚,下面我就通过举例的方式来具体讲解 foreach 原理。


在这里我们首先自定义一个类型 Cat 并遍历这个类型:

//定义 Cat 类型
class Cat
{
}
//遍历 Cat
class Program
{
    static void Main(string[] args)
    {
        Cat cat = new Cat();
        foreach(var item in cat)
        {
            //more code
        }
    }
}

我们运行上述代码后编译器会提示错误 “Cat” 不包含 “GetEnumerator” 的公共定义,因此 foreach 语句不能作用于 “Cat” 类型的变量,由此错误提示我们可以得知如果 Cat 类型可以被 foreach 遍历,那么 Cat 类就必须实现 GetEnumerator 方法。下面我们就在 Cat 类中加入 GetEnumerator 方法。

class Cat
{
    //加入 GetEnumerator 方法的实现
    public object GetEnumerator()
    {
        return null;
    }
}

我们再次运行代码,这时程序出现如下两个错误提示:


  • foreach 要求 “Cat.GetEnumerator()”的返回类型 “object”必须具有适当的公共 MoveNext 方法和公共 Current 属性;
  • object 并不包含 “MoveNext” 的定义。


根据上述错误提示我们可以推断出 GetEnumerator 方法的返回值必须要有 MoveNext 方法和 Current 属性。但是我们目前并不知道 GetEnumerator 方法的返回值类型和 Current 属性是否是只读的,这种情况我们该怎么办呢?此时我们可以查看已经支持 foreach 遍历的类型是怎么做的,下面的代码段展示了 string 类型是如何实现的(只列出了关键代码)。

//more code
public CharEnumerator GetEnumerator();
//more code
pubic sealed class CharEnumerator:ICloneabe,IEnumerator<char>,IEnumerator,IDisposable
{
    public char Current {get;}
    //more code
    public bool MoveNext();
    //more code
}

根据上述代码段我们仿写如下:

class Cat
{
    public CatEnumerator GetEnumerator()
    {
        return new CatEnumerator();
    }
}
class CatEnumerator
{
    public char Current {get;}
    public bool MoveNext()
    {
        return true;
    }
}

这时我们编译发现原来的错误已经消失了,程序编译通过了。但是不要以为到这里就完了,Cat 类仅仅包含这些是没有任何意义的,这些内容只是为了让程序通过编译而已,在实际开发中我们遍历的对象是一个序列,那么我们现在就在 Cat 类中添加一个固定的序列:

class Cat
{
    string[] datas=new string[]{"波斯猫","狸花猫","无毛猫","虎斑猫"};
    public CatEnumerator GetEnumerator()
    {
        return new CatEnumerator();
    }
}

我们已经添加了数据对象,那么 foreach 是如何访问到这个数据的呢?这时我们可以将数据对象通过 GetEnumerator 方法作为迭代计数器对象(CatEnumerator)构造函数的参数传递进去,然后迭代计数器对象提供一个属性将这些数据存储起来。

class Cat
{
    string[] datas=new string[]{"波斯猫","狸花猫","无毛猫","虎斑猫"};
    public CatEnumerator GetEnumerator()
    {
        return new CatEnumerator(datas);
    }
}
class CatEnumerator
{
    //存储数据
    private string[] datas;
    //带参构造函数
    public CatEnumerator(string[] datas)
    {
        this.datas=datas;
    }
    public char Current {get;}
    public bool MoveNext()
    {
        return true;
    }
}

到目前为止我们已经设置了遍历的数据,如果要将数据遍历出来还需要一个下标索引来读取数组中的每个元素,并将每次读取出来的元素值赋值给 Current 属性。我们可以在迭代计数器对象中定义一个 index 整型私有属性作为下标索引属性,这里需要注意的是我们 index 这个属性的默认值为 -1 ,这一点是很多新手开发人员比较容易出错的地方。既然有下标了,我们在遍历的时候下标就必须是递增变化,不断指向下一个元素的位置直到到达数组的末端为止。这时我们就需要在 MoveNext 方法中进行执行下标递增的操作了,MoveNext 方法是一个返回值为 bool 类型的方法,其目的是告知 foreach 但钱遍历的数据对象是否存在还未遍历到的元素,如果存在就返回 true 反之返回 false 遍历结束。下面我们针对这一段所说的内容进行代码编写。

class CatEnumerator
{
    //存储数据
    private string[] datas;
    //带参构造函数
    public CatEnumerator(string[] datas)
    {
        this.datas=datas;
    }
    //数组下标
    private int index=-1;
    //遍历当前元素
    public char Current 
    {
        get
        {
            return datas[index];
        }
    }
    public bool MoveNext()
    {
        index++;
        return index < datas.Length;
        return true;
    }
}

到目前为止我们就编写了一个可以通过 foreach 遍历的类型,这里有三点很重要:


  • GetEnumerator 方法的作用是 foreach 调用当前需要遍历的类型的迭代计数器对象,该方法的返回类型为用于foreach 遍历的迭代计数器对象;
  • Current 属性就是当前遍历到的对象;
  • MoveNext 方法促使迭代计数器对象的计数移动到下一位。


通过前面所述的内容,我们可知 foreach 遍历主要有三个步骤:


  • foreach 调用当前可遍历类型的 GetEnumerator 方法创建一个迭代计数器对象,并将要遍历的数据传递给迭代计数器对象的构造函数中;
  • 迭代计数器对象调用它 MoveNext 方法将所以小标递增 1 ,若下标大于数据长度则迭代完成;
  • MoveNext 方法返回 true 并返回 Current 属性中存储的数据。


以上三个步骤总结起来就是 获取迭代计数器对象 >> 调用 MoveNext 方法 >> 获取 Current 属性


小技巧:在 c# 中如果要查看某个类型是否支持 foreach 我们可以查看还类型和该类型的迭代计数器是否都实现了 IEnumerable 接口,因为 IEnumerable 接口中的就包含了 foreach 实现的原理和必须调用的成员。


目录
相关文章
|
7月前
|
编译器 数据处理 C#
C#中的异步流:使用IAsyncEnumerable<T>和await foreach实现异步数据迭代
【1月更文挑战第10天】本文介绍了C#中异步流的概念,并通过使用IAsyncEnumerable<T>接口和await foreach语句,详细阐述了如何异步地迭代数据流。异步流为处理大量数据或需要流式处理数据的场景提供了一种高效且非阻塞性的方法,使得开发者能够更优雅地处理并发和数据流问题。
|
7月前
|
网络协议 API C#
C# 中模拟 POST 和 GET 请求的原理与实践
【1月更文挑战第4天】在现代网络应用中,HTTP请求是客户端与服务器交互的基础。其中,GET和POST是最常用的两种请求方法。本文将介绍如何使用C#语言模拟这两种请求,并解释其背后的工作原理。我们将利用.NET框架中的HttpClient类来发送请求,并处理服务器的响应。通过本文,读者将能够理解HTTP请求的基本构成,学会在C#中编写代码来模拟这些请求,进而在开发过程中实现与Web服务的交互。
|
1月前
|
安全 编译器 程序员
C# 中 foreach 循环和 for 循环深度比较
为什么建议你多数情况下使用 foreach 进行遍历循环?看完你就明白了
|
存储 C#
C# 逻辑位运符及运算原理 按位操作二进制
C# 逻辑位运符及运算原理 按位操作二进制
C#中For循环和Foreach循环的区别
C#中For循环和Foreach循环的区别
135 0
|
7月前
|
C# 图形学
【Unity 3D】C#中while do while for foreach等循环语句的讲解(附测试代码)
【Unity 3D】C#中while do while for foreach等循环语句的讲解(附测试代码)
253 0
C# for和foreach两种循环的效率问题
C# for和foreach两种循环的效率问题
c#之Attribute特性的原理
c#之Attribute特性的原理
71 0
|
C# 图形学
【unity之c#】所以迭代器的原理知识你还清楚吗?
【unity之c#】所以迭代器的原理知识你还清楚吗?
144 0
|
算法 C#
【愚公系列】2021年11月 C#版 数据结构与算法解析 for和foreach性能分析
【愚公系列】2021年11月 C#版 数据结构与算法解析 for和foreach性能分析
162 0
【愚公系列】2021年11月 C#版 数据结构与算法解析 for和foreach性能分析