前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >小解c# foreach原理

小解c# foreach原理

原创
作者头像
喵叔
修改2020-10-19 10:14:53
1K0
修改2020-10-19 10:14:53
举报
文章被收录于专栏:喵叔's 专栏

原创声明:本文首发于 51CTO,如需转载请联系我

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

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

代码语言:txt
复制
//定义 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 方法。

代码语言:txt
复制
class Cat
{
    //加入 GetEnumerator 方法的实现
    public object GetEnumerator()
    {
        return null;
    }
}

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

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

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

代码语言:txt
复制
//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
}

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

代码语言:txt
复制
class Cat
{
    public CatEnumerator GetEnumerator()
    {
        return new CatEnumerator();
    }
}
class CatEnumerator
{
    public char Current {get;}
    public bool MoveNext()
    {
        return true;
    }
}

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

代码语言:txt
复制
class Cat
{
    string[] datas=new string[]{"波斯猫","狸花猫","无毛猫","虎斑猫"};
    public CatEnumerator GetEnumerator()
    {
        return new CatEnumerator();
    }
}

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

代码语言:txt
复制
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 遍历结束。下面我们针对这一段所说的内容进行代码编写。

代码语言:txt
复制
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 实现的原理和必须调用的成员。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档