C#中foreach的实现原理

2023-11-07

C#中foreach的实现原理

2017年04月01日 17:57:02 阅读数:3155更多

个人分类: C#

在探讨foreach如何内部如何实现这个问题之前,我们需要理解两个C#里边的接口,IEnumerable 与 IEnumerator. 在C#里边的遍历集合时用到的相关类中,IEnumerable是最基本的接口。这是一个可以进行泛型化的接口,比如说IEnumerable<User>.在微软的.NET推出了这两个接口后,才有了foreach的用法,可以说,foreach是建立在这两个接口的基础之上的,foreach的前提是其里边的容器要实现了IEnumerable接口。

 

 

IEnumerable 这个接口里边定义的内容非常简单,最重要的就是里边有一个抽象方法GetEnumerator. IEnumerable的意思是这个集合是可以遍历的,而这个GetEnumerator方法返回的IEnumerator的就是一个遍历器,用这个工具来遍历这个类。如果说IEnumerable 是一瓶香槟,那么IEnumerator就是一个开瓶器。在实现这个IEnumerable接口的时候,必须要实现这个GetEnumerator方法,要返回一个实例化的IEnumorator. 

 

下面来介绍一下这个IEnumorator接口。这个接口中定义的内容也很简单,包括Current,就是返回这个遍历工具所指向的那个容器的当前的元素,MoveNext 方法就是指向下一个元素,当遍历到最后没有元素时,返回一个false.当我们实现一个IEnumerable类的时候,我们的目的就应该是遍历这个集合,所以同时我们要实现IEnumerator这个工具类,定义我们自己的逻辑来告诉CLR我们怎么去遍历这个集合。 

 

 

下面是一个简单的例子,来说明一下这两个接口的用法。

 

[csharp] view plain copy

  1. // Person类包括两个属性。  
  2. public class Person  
  3. {  
  4.     public Person(string fName, string lName)  
  5.     {  
  6.         this.firstName = fName;  
  7.         this.lastName = lName;  
  8.     }  
  9.     public string firstName;  
  10.     public string lastName;  
  11. }  
  12. //People类就是Person的集合,里边使用了一个数组把单个的对象存在这个数组当中。而且因为实现了  
  13. //IEnumerable接口,所以要实现GetEnumerator方法,返回一个实现了IEnumerator的类。  
  14. public class People : IEnumerable  
  15. {  
  16.     private Person[] _people;  
  17.     public People(Person[] pArray)  
  18.     {  
  19.         _people = new Person[pArray.Length];  
  20.         for (int i = 0; i < pArray.Length; i++)  
  21.         {  
  22.             _people[i] = pArray[i];  
  23.         }  
  24.     }  
  25.     public PeopleEnum GetEnumerator()  
  26.     {  
  27.         return new PeopleEnum(_people);  
  28.     }  
  29. }  
  30. // 这里我们需要定义一套逻辑去遍历上边的集合。  
  31. public class PeopleEnum : IEnumerator  
  32. {  
  33.     public Person[] _people;  
  34.   
  35.     int position = -1;  
  36.     public PeopleEnum(Person[] list)  
  37.     {  
  38.         _people = list;  
  39.     }  
  40.     public bool MoveNext()  
  41.     {  
  42.         position++;  
  43.         return (position < _people.Length);  
  44.     }  
  45.     public void Reset()  
  46.     {  
  47.         position = -1;  
  48.     }  
  49.     object IEnumerator.Current  
  50.     {  
  51.         get  
  52.         {  
  53.             return Current;  
  54.         }  
  55.     }  
  56.     public Person Current  
  57.     {  
  58.         get  
  59.         {  
  60.             try  
  61.             {  
  62.                 return _people[position];  
  63.             }  
  64.             catch (IndexOutOfRangeException)  
  65.             {  
  66.                 throw new InvalidOperationException();  
  67.             }  
  68.         }  
  69.     }  
  70. }  
  71. class App  
  72. {  
  73.     static void Main()  
  74.     {  
  75.         Person[] peopleArray = new Person[3]  
  76.         {  
  77.             new Person("John", "Smith"),  
  78.             new Person("Jim", "Johnson"),  
  79.             new Person("Sue", "Rabon"),  
  80.         };  
  81.         People peopleList = new People(peopleArray);  
  82.   
  83.         //在这里使用foreach就可以遍历这个集合了,因为它实现了IEnumerable接口。  
  84.         foreach (Person p in peopleList)  
  85.             Console.WriteLine(p.firstName + " " + p.lastName);  
  86.      }  
  87. }  
  88. /* This code produces output similar to the following: 
  89.  * 
  90.  * John Smith 
  91.  * Jim Johnson 
  92.  * Sue Rabon 
  93.  * 
  94.  */  


如果说,foreach后台的逻辑是这么实现的?大概是这个样子的。上边的代码会被CLR翻译成这样。

 

 

 

[csharp] view plain copy

  1. foreach (Person p in peopleList)  
  2.             Console.WriteLine(p.firstName + " " + p.lastName);  
  3. //翻译成  
  4.   
  5. IEnumerator enumerator = (peopleList).GetEnumerator();  
  6. try {  
  7.       while (enumerator.MoveNext()) {  
  8.       Person element; //post C# 5  
  9.       element = (Person )enumerator.Current;  
  10.     //下边这句就是原来foreach方法体中的逻辑  
  11.       Console.WriteLine(p.firstName + " " + p.lastName);  
  12.    }  
  13. }  
  14. finally {  
  15.    IDisposable disposable = enumerator as System.IDisposable;  
  16.    if (disposable != null) disposable.Dispose();  
  17. }  

 

 

附加:关于IEnumerable与ORM框架联合使用时候的延迟加载问题,以及Resharper对于此接口mutiple enumeration警告问题

 

使用IEnumerable的时候,Resharper经常会提示这个问题?这个问题意思是,这个集合对象可能会返回不同的遍历结果。

 

因为IEnumerable另外一个功能就是存放SQL一类的查询逻辑,注意,这里指的是查询逻辑,而不是真正的查询结果,也就是延迟加载。以下边的例子为例,可能objects中存放的是SQL查询逻辑。当第一次调用Any()方法的时候,会调用SQL语句查询到数据库中的结果,这时候是有一条数据的。但是objects调用First()方法来获取这个记录的时候,可能这时候的数据库已经被其他的程序改了,没有数据了,这时候就出现了dirty data的问题。所以,为了数据的一致性,需要在使用IEnumeralbe的时候,调用.ToList()方法把这些内容存放在一个个实实在在的容器中,这样就前后一致了。 

还有就是从代码性能角度考虑,每次都调用一下数据库会很慢,所以干脆一下全部把数据库中符合条件的结果放到内存List中,用的时候直接从内存中拿就快多了。

 

[csharp] view plain copy

  1. public List<object> Foo(IEnumerable<object> objects)  
  2. {  
  3.     if(objects == null || !objects.Any())  
  4.         throw new ArgumentException();  
  5. var firstObject = objects.First();  
  6.     var list= DoSomeThing(firstObject);          
  7.     var secondList = DoSomeThingElse(objects);  
  8.     list.AddRange(secondList);  
  9. return list;  
  10. }  
  11.    

 

 

 IEnumerable在一些ORM框架中实现了延迟加载的功能。比如说,在框架自己定义的容器对象中,实现了特定的IEnumerator接口,在MoveNext中指定逻辑,连接数据库,获取对象等。

 

注:C#中Dictionary字典类介绍:http://www.cnblogs.com/txw1958/archive/2012/11/07/csharp-dictionary.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#中foreach的实现原理 的相关文章

  • 将反序列化方法转换为异步

    我正在尝试使用 Async Await 转换此将对象反序列化为字符串的方法 public static T DeserializeObject
  • 将 Google 云端硬盘访问权限委派给服务帐户失败

    我参与构建了一个内部使用的应用程序 用户可以通过该应用程序上传文件 并将其存储在 Google Drive 中 由于建议不要使用服务帐户作为文件所有者 因此我希望代表公司系统管理员有权访问的指定用户帐户上传应用程序 我已经创建了该应用程序以
  • 替换大字符串中的多个字符串的最快方法

    我正在寻找替换大 1mb 字符串的多个 500 子字符串的最快方法 无论我尝试过什么 String Replace 似乎都是最快的方法 我只关心最快的方式 不是代码的可读性 可维护性等 我不在乎是否需要使用不安全的代码或预处理原始字符串 每
  • 如何正确使用memcpy?

    我有一个mainbuf bufsize 最初为空 我正在阅读一些输入 read fd otherbuf sizeof otherbuf 分配给不同的字符串otherbuf 每次我分配一个新字符串给otherbuf我想将其附加到mainbuf
  • 如何在 Multiline 属性设置为 true 的文本框中将空格替换为换行符?

    假设我有这个字符串 string str The quick brown fox jumps over the lazy dog 如何替换或忽略字符串中的空格并在多行文本框中输入每个单词 预期输出 The quick brown fox j
  • 在 C 中使用模板函数的最短示例?

    我如何处理函数echo tpl可以采取1类型参数int or string 然后打印出来 C没有模板 我认为你能做的最好的事情就是使用联合或让函数具有不同的名称 后一种具有不同名称的方法是准标准方法 例如fabs fabsf fabsl a
  • 什么是合适的 NHibernate / Iesi.Collections.Generic.ISet 替代品?

    在最新版本的 Iesi Collections 中缺少 Iesi Collections Generic ISet 似乎有三种选择 链接哈希集 只读集 同步集 Iesi Collections Generic ReadOnlySet 似乎最
  • C++ 局部变量销毁顺序

    C 11 中是否存在局部变量释放的定义顺序 更简洁地说 同一作用域中两个局部变量的析构函数的副作用将以什么顺序变得可见 e g struct X X do something int main X x1 X x2 return 0 Is x
  • main() 是用户定义函数吗? [复制]

    这个问题在这里已经有答案了 程序员does定义内部发生的事情main 毕竟 那么 它应该被视为用户定义的函数吗 C 标准没有用户定义函数的概念 相反 它有一个概念库函数 main 不是库函数 但是 该标准还对其签名提出了一些要求 并且不得重
  • 为什么要在 C 和 C++ 项目中创建 include/ 目录?

    当我处理我的个人 C 和 C 项目时 我通常把file h and file cpp在同一目录中 然后file cpp可以参考file h with a include file h 指示 然而 通常会发现库和其他类型的项目 如 linux
  • StreamReader 的默认值是多少?

    我需要使用这个构造函数public StreamReader Stream stream Encoding encoding bool detectEncodingFromByteOrderMarks int bufferSize bool
  • 编译器错误? g++ 允许可变大小的静态数组,除非函数是模板化的

    下面的代码演示了我无法解释的 gcc 4 6 2 的行为 第一个函数声明一个 vec t 类型的静态数组 其中 vec t 是 unsigned char 的 typedef 别名 第二个函数是相同的 只是 vect t 的类型是模板参数
  • iText7 RegexBasedLocationExtractionStrategy 如何获取找到的文本的字体名和字体大小

    我尝试在 C 上使用 iText7 进行文本替换 我只能使用 RegexBasedLocationExtractionStrategy 获取搜索文本的内容和矩形 并且我想获取文本的字体和大小 有什么建议么 谢谢 你可以实施IText提取策略
  • 开始学习 C# 的最佳方式是什么?

    我对 vb 6 有一点编程经验 而 vb net 则不多 请告诉我成为专家 C 程序员的最佳方法 我知道这需要很长时间 想想你如何学习人类语言 阅读 写作 口语和听力 阅读代码 阅读文章 阅读示例 当您更有经验时 请查看您使用的一些项目的源
  • 我不明白这个霍夫曼算法的实现

    template
  • 对双向链表进行排序 C++

    尝试通过遍历列表的循环来完成此操作 在循环中 我将头节点输入到我定义的排序函数中 然后使用 strcmp 来确定节点中的哪个名称是否应该排在前面 它不起作用 因为写得太早了 我通过一次沿着列表一个节点进行线性比较 而不是回去查看第一个节点是
  • 网页上的富文本编辑器

    我正在尝试在我的网页中添加一个富文本编辑器 用户可以在其中撰写评论并格式化他们所写的内容 类似于我们在此网站上撰写帖子的编辑器 谁能指出我关于此的正确方向 任何可以帮助我构建这样一个组件的教程 我还想要一个免费的产品 忘记之前提到 类似的东
  • 在 Linux 上用 C 跟踪键盘和鼠标事件

    如何在 Linux 中用 C 语言跟踪键盘或鼠标事件 Like for example if the user presses ESC Shift etc I should be able to track it Same way for
  • Action 的通用约束未按预期工作

    我无法理解为什么以下代码片段没有给我错误 public void SomeMethod
  • 着色器可以旋转形状以面向相机吗?

    我制作了一个球出现在 3D 空间中的场景 三角球耗费大量资源 所以我使用带有球纹理的二维表面 四边形 来完成此操作 但现在我需要在每次相机移动时调整形状的方向 我使用位置变换和 LookAt 方法来完成此操作 问题是我可以优化这个吗 如果可

随机推荐

  • LeetCode 2363. 合并相似的物品

    给你两个二维整数数组 items1 和 items2 表示两个物品集合 每个数组 items 有以下特质 items i value i i i weight i
  • 恒指市场新手的困惑,最新战法来解决。

    1 作为一个交易者 要想取得非凡成功 须具备哪些素质呢 所有取得辉煌成就的伟大的交易者都有着以下共同的特征及基本素质 1 坚韧的性格与成熟的心态 2 疯狂的热忱与专注 3 经市场充分验证了的盈利概率及赢利模式 4 铁的执行力 5 锲而不舍的
  • Openwrt 定制版修改固件显示信息

    在 usr lib lua luci version lua中找到 文件储存在 etc openwrt release上 local pcall dofile G pcall dofile G module luci version if
  • [974]python execjs execjs._exceptions.ProgramError: ReferenceError: navigator is not defined

    问题 execjs exceptions ProgramError ReferenceError navigator is not defined 解决办法 在js文档头部添加如下代码 global navigator userAgent
  • Docker解读(什么是容器)

    一 What Is A Container 容器映像是一个软件的轻量级独立可执行软件包 包含运行它所需的一切 代码 运行时 系统工具 系统库 设置 不管环境如何 集装箱化软件都可以运行相同的Linux和Windows应用程序 容器将软件与其
  • Cython编译python为so 代码加密

    1 编译出来的so比网上流传的其他方法小很多 2 language level 是python的主版本号 如果python版本是2 x 目前的版本Cython需要人工指定language level 3 python setup py bu
  • 全网最详细charles抓包工具详细教程,实战教程(细致)

    目录 导读 一 前言 二 在PC端抓https包 三 在PC端抓https包 四 在移动端抓http包 五 在移动端抓https包 一 前言 charles相当于一个插在服务器和客户端之间的 过滤器 当客户端向服务器发起请求的时候 先到ch
  • 英伟达GPU 解码&编码 能力

    来源https en wikipedia org wiki Nvidia NVDEC
  • Zygisk-Il2CppDumper 使用Android Studio运行gradle任务:module:assembleRelease编译

    使用Android Studio运行gradle任务 module assembleRelease编译 zip包会生成在out文件夹下 记录一下编译过程 虽然直接在GIT网上进行编译成功了 但还是想自己通过Android Studio来进行
  • wxWidgets开发之多线程wxThread编程

    上节说到使用wxCondition来实现某一消息处理的业务场景的多线程处理方法 在此之前先分享一下wxCondition用法 条件变量 最常用在多线程环境下 用来指示当前所在线程的某些条件已经满足 其他线程可以共享该线程的数据 或者去完成预
  • angular学习-自定义组件

    angular学习 自定义组件 1 命令ng g整理 2 自定义组件创建 3 自定义组件的使用 1 命令ng g整理 这个帖子讲的非常清楚 可以看一下 https www cnblogs com ckAng p 6693702 html 2
  • VSCode中开发JavaWeb项目(Maven+Tomcat+热部署)

    1 安装插件 首先需要安装所用到的插件 分别用来支持Java 热部署和Tomcat服务器的插件 在插件市场中搜索Java 第一个就是Extension Pack for Java 内置了6个依赖插件 直接一键安装即可 然后是热部署插件 市场
  • 在Idea中使用Maven/Git

    Idea中配置第三方Maven settings gt build Execution Deployment gt Build Tools gt Maven 并且将自动导入也勾选上 Idea也可以使用自带的Maven插件使用默认的Maven
  • MATLAB——Matlab R2018b软件安装教程

    Matlab R2018b软件安装教程 1 选中 Matlab R2018b 压缩包 鼠标右击选择 解压到Matlab R2018b 2 双击打开 Matlab R2018b 文件夹 3 双击打开 R2018b win64 文件夹 4 选中
  • 测试开发——selenium1

    selenium1 1 什么是自动化测试 1 1 单元测试 1 2 接口测试 自动化的价值 脚本的复用率 复用率越高 价值越大 1 3 UI自动化 2 UI自动化的好处 3 自动化框架 4 webdriver的原理 5 selenium I
  • Android 入门(四)

    文章目录 Intent 显式 Intent 定义两个 xml 文件 android orientation match parent 和 wrap content Intent函数 定义两个 Activity 隐式 Intent 更多隐式
  • vim编辑器ctrl+s卡住假死

    在linux的vim编辑器编辑代码时 ctrl s会导致vim界面卡住 ctrl s在bash中是锁屏命令 通过ctrl q可解锁
  • 03【深度学习】YOLOV3-WIN11环境搭建(配置+训练)

    一 深度学习 YOLOV3 WIN11环境搭建 本篇文字是 深度学习 YOLOV5 WIN11环境搭建 配置 训练 首先介绍win11下 基于Anaconda pytorch的YOLOV5深度学习环境搭建 环境配置顺序 显卡驱动 CUDA
  • 十六进制转换成八进制

    问题描述 从键盘输入一个不超过8位的正的十六进制数字符串 将它转换为正的十进制数后输出 注 十六进制数中的10 15分别用大写的英文字母A B C D E F表示 样例输入 FFFF 样例输出 65535 include
  • C#中foreach的实现原理

    C 中foreach的实现原理 2017年04月01日 17 57 02 阅读数 3155更多 个人分类 C 在探讨foreach如何内部如何实现这个问题之前 我们需要理解两个C 里边的接口 IEnumerable 与 IEnumerato