C#中foreach语句深入研究
作者:junjie 发布时间:2022-11-15 00:30:17
1、概述
本文通过手动实现迭代器来了解foreach语句的本质。
2、使用foreach语句遍历集合
在C#中,使用foreach语句来遍历集合。foreach语句是微软提供的语法糖,使用它可以简化C#内置迭代器的使用复杂性。编译foreach语句,会生成调用GetEnumerator和MoveNext方法以及Current属性的代码,这些方法和属性恰是C#内置迭代器所提供的。下面将通过实例来说明这一切。
例1:使用foreach来遍历集合
//************************************************************
//
// foreach应用示例代码
//
// Author:三五月儿
//
// Date:2014/09/10
//
//
//************************************************************
using System;
using System.Collections;
using System.Collections.Generic;
namespace IEnumerableExp
{
class Program
{
static void Main(string[] args)
{
List<Student> studentList = new List<Student>()
{
new Student(){Id = 1, Name = "三五月儿", Age = 23},
new Student(){Id = 2, Name = "张三丰", Age = 108},
new Student(){Id = 3, Name = "艾尔克森", Age = 25},
new Student(){Id = 3, Name = "穆里奇", Age = 27}
};
foreach (var student in studentList)
{
Console.WriteLine("Id = {0}, Name = {1}, Age = {2}", student.Id,student.Name,student.Age);
}
}
}
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}
代码中,使用foreach语句遍历Student对象的集合,依次输出Student对象的Id,Name,Age属性值。使用ILDASM查看程序对应的IL代码,下面这些是与foreach语句相关的IL代码:
IL_00c6: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<class IEnumerableExp.Student>::GetEnumerator()
IL_00d1: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class IEnumerableExp.Student>::get_Current()
IL_0102: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class IEnumerableExp.Student>::MoveNext()
在IL代码中,是不是找到了GetEnumerator和MoveNext方法以及Current属性的身影,可见:foreach语句确实是微软提供的用来支持C#内置迭代器操作的语法糖,因为这些方法和属性正是C#内置迭代器所提供的。
当然,除了使用foreach语句来遍历集合外,还可以使用C#内置迭代器提供的方法和属性来遍历集合,本例中还可以使用下面的代码来完成遍历操作:
IEnumerator<Student> studentEnumerator = studentList.GetEnumerator();
while (studentEnumerator.MoveNext())
{
var currentStudent = studentEnumerator.Current as Student;
Console.WriteLine("Id = {0}, Name = {1}, Age = {2}", currentStudent.Id, currentStudent.Name, currentStudent.Age);
}
在第二种方法中,通过调用GetEnumerator和MoveNext方法以及Current属性来完成遍历操作,是不是与foreach语句编译后生成的代码一致啊。
两种遍历方法,都会得到下图所示结果:
图1 遍历集合元素
查看代码中GetEnumerator和MoveNext方法以及Current属性的定义,发现GetEnumerator方法来自于IEnumerable接口,而MoveNext方法与Current属性来自于IEnumerator接口。实现C#迭代器都应该实现这两个接口。下面就手动实现一个迭代器来操作学生对象的集合。
3、手动实现一个迭代器
前面使用到的是C#内置迭代器,当然,我们完全可以手动实现一个自己的迭代器。
例2:手动实现迭代器
//************************************************************
//
// foreach应用示例代码
//
// Author:三五月儿
//
// Date:2014/09/10
//
//
//************************************************************
using System;
using System.Collections;
using System.Collections.Generic;
namespace IEnumerableExp
{
class Program
{
static void Main(string[] args)
{
Student[] students = new Student[4]
{
new Student(){Id = 1, Name = "三五月儿", Age = 23},
new Student(){Id = 2, Name = "张三丰", Age = 108},
new Student(){Id = 3, Name = "艾尔克森", Age = 25},
new Student(){Id = 3, Name = "穆里奇", Age = 27}
};
StudentSet studentSet = new StudentSet(students);
foreach (var student in studentSet)
{
Console.WriteLine("Id = {0}, Name = {1}, Age = {2}", student.Id, student.Name, student.Age);
}
}
}
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class StudentSet : IEnumerable
{
private Student[] students;
public StudentSet(Student[] inputStudents)
{
students = new Student[inputStudents.Length];
for (int i = 0; i < inputStudents.Length; i++)
{
students[i] = inputStudents[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
public StudentEnumerator GetEnumerator()
{
return new StudentEnumerator(students);
}
}
public class StudentEnumerator : IEnumerator
{
public Student[] students;
int position = -1;
public StudentEnumerator(Student[] students)
{
this.students = students;
}
public bool MoveNext()
{
position++;
return (position < students.Length);
}
public void Reset()
{
position = -1;
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public Student Current
{
get
{
try
{
return students[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
}
}
代码中定义学生集合类StudentSet,在类中使用Student类型的数组来保存学生元素,该类实现IEnumerable接口,所以StudentSet类必须实现IEnumerable接口的GetEnumerator方法,该方法返回实现了IEnumerator接口的迭代器StudentEnumerator。
下面来看看StudentEnumerator类的定义,StudentEnumerator表示遍历学生集合的迭代器,使用它提供的方法和属性可以遍历集合的元素,该类实现IEnumerator接口,所以必须实现IEnumerator接口提供的MoveNext和Reset方法以及Current属性。StudentEnumerator类使用Student类型的集合students来保存需要遍历的集合。使用私有变量position来记录元素的位置,一开始position被赋值为-1,定位于集合中第一个元素的前面,在Reset方法中也可以将position的值置为-1,表示回到遍历操作前的状态。在MoveNext方法中先将position加1,再将其与集合的长度进行比较,看是否已经遍历完了所有元素,若未完返回true,否则返回false。在只读属性Current的实现中通过代码students[position]返回students集合中position位置的元素值。在使用迭代器时,需要先调用MoveNext方法判断下一个元素是否存在,如存在使用Current属性得到这个值,若不存在则表示已经遍历完所有元素,将停止遍历操作。
代码中同样使用foreach语句来遍历StudentSet对象中的元素并输出,与使用内置迭代器的效果一致。
4、总结
实现迭代器需要借助于IEnumerable与IEnumerator接口,接口IEnumerator提供的方法GetEnumerator可以返回实现IEnumerator接口的迭代器,而IEnumerator接口中包含了实现迭代器所需的方法及属性的定义。凡是实现了迭代器的类都可以使用foreach语句来遍历其元素,因为foreach语句是微软提供的支持内置迭代器的语法糖,编译foreach语句后生成的代码与使用迭代器的代码完全一致。


猜你喜欢
- 一、使用@Profile1.1、@Profile修饰类开发环境package com.example.demo.config;import
- 质数又称素数。一个大于1的自然数,如果除了1和它自身外,不能被其他自然数整除的数;否则称为合数。根据算术基本定理,每一个比1大的整数,要么本
- MVC三层架构我们在刚刚成为程序员的时候,就会被前辈们 “教育” 说系统的设计要遵循 MVC(Model-View-Controller)架
- 本文实例讲述了Android网络数据开关用法。分享给大家供大家参考,具体如下:api中没有开放这部分接口。因此大家可以使用这个方法,true
- 前言:GraphQL既是API查询语言,也是使用当前数据执行这些查询的运行时。GraphQL让客户能够准确地要求他们所需要的东西,仅此而已,
- 一、引言“为什么我们需要掌握互操作技术的呢?” 对于这个问题的解释就是—&
- 1.由json字符串转换成Map对象如json字符串:{"contend":[{"bid":&quo
- public class MainActivity extends Activity { TextView tv; Ch
- kafka消费不到数据的排查集群上新安装并启动了3个kafka Broker,代码打包上传至集群,运行后发现一直消费不到数据,本地
- 以下弹出框是框的实现,放入到SWT项目下就可运行。1.提示框MessageBox mb = new MessageBox(shell,SWT
- 本篇给大家详细讲解了MTKAndroid平台开发流程,大致分为44个步骤,我们把每个步骤的命令详细讲解了下,一起来学习下。1.拷贝代码仓库从
- 前言单例模式最初的定义出现于《设计模式》(艾迪生维斯理, 1994):“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”而我对单例的
- Android Studio配置Kotlin开发环境详细步骤第一步:安装Kotlin插件打开Settings面板,找到Plugins选项,点
- 在开发程序的过程中,难免少不了写入错误日志这个关键功能。实现这个功能,可以选择使用第三方日志插件,也可以选择使用数据库,还可以自己写个简单的
- 声明一个可变数量的参数: Static int Add(params int[] values) { int sum = 0; if(val
- 本文设计一个简单的班级管理系统,满足如下要求:1、设计学生类Student,包含学号(String型)、姓名(String型)、
- 一.一维数组的定义1.创建数组数组定义有三种方法:int[] array1 = new int[10];//前面的int[]为数组的类型,后
- 本文实例讲述了C#中DataSet转化为实体集合类的方法,分享给大家供大家参考。具体实现方法如下:/// <summary>//
- 废话不多说,直接上代码String longUrl = "https://open.weixin.qq.com/connect/o
- 什么是MybatisMyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software fou