1.介绍

我们知道,我们要使一个类型支持foreach循环,就需要这个类型满足下面条件之一:

  • 该类型实例如果实现了下列接口中的其中之一:

    • System.Collections.IEnumerable
    • System.Collections.Generic.IEnumerable<T>
    • System.Collections.Generic.IAsyncEnumerable<T>
  • 该类型中有公开的无参GetEnumerator()方法,且其返回值类型必须是类,结构或者接口,同时返回值类型具有公共 Current 属性和公共无参数且返回类型为 Boolean的MoveNext 方法。

上面的第一个条件,归根结底还是第二个条件的要求,因为这几个接口,里面要求实现的还是GetEnumerator方法,同时,接口中GetEnumerator的返回值类型IEnumerator接口中要实现的成员和第二条中返回值类型的成员相同。

C#9.0之前,是不支持采取扩展方法的方式给类型注入GetEnumerator方法,以支持foreach循环的。从C#9.0之后,这种情况得到了支持。

2. 应用与示例

在这里,我们定义一个People类,它可以枚举其所有组员Person,并且在其中定义了MoveNext方法和Current属性。同时,我们也通过扩展方法给People注入了GetEnumerator方法。这样,我们就可以使用foreach来枚举People对象了。

首先,我们来定义一个Person记录:

public record Person(string FirstName, string LastName);

下来,我们来创建People类型,用来描述多个Person对象,并提供GetEnumerator返回值类型中所需的Current属性和MoveNext方法。在此,我们没有实现任何接口:

public class People:IDisposable//: IEnumerator<Person>
{
int position = -1; private Person[] _people { get; init; }
public People(Person[] people)
{
_people = people;
} public bool MoveNext()
{
position++;
return (position < _people.Length);
} public Person Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
} public void Reset()
{
position = -1;
} public void Dispose()
{
Reset();
}
}

需要注意的是People中,由于没有通过使用前面的接口来实现支持foreach功能,这样就存在一个问题,就是第一次foreach循环完成后,状态还没有恢复到初始状态,第二次使用foreach进行枚举就没有可用项。因此我们添加了Reset方法用于手工恢复回初始状态,如果想让foreach能自动恢复状态,就让People实现接口IDisposable,并在其实现中,调用Reset方法。

然后,我们定义扩展方法,给People注入GetEnumerator方法

static class PeopleExtensions
{
//public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> people) => people;
public static People GetEnumerator(this People people) => people;
}

最后,只要引用了扩展方法所在的命名空间,foreach循环就可以使用了。

var PersonList = new Person[3]
{
new ("John", "Smith"),
new ("Jim", "Johnson"),
new ("Sue", "Rabon"),
}; var people = new People(PersonList);
foreach (var person in people)
{
Console.WriteLine(person);
}

到这里,我们就完成了利用扩展方法来实现foreach循环的示例,为了方便拷贝测试,我们所有的代码放在一起就如下所示:

var PersonList = new Person[3]
{
new ("John", "Smith"),
new ("Jim", "Johnson"),
new ("Sue", "Rabon"),
}; var people = new People(PersonList);
foreach (var person in people)
{
Console.WriteLine(person);
} public record Person(string FirstName, string LastName); public class People:IDisposable//: IEnumerator<Person>
{
int position = -1; private Person[] _people { get; init; }
public People(Person[] people)
{
_people = people;
} public bool MoveNext()
{
position++;
return (position < _people.Length);
} public Person Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
} public void Reset()
{
position = -1;
} public void Dispose()
{
Reset();
}
} static class PeopleExtensions
{
//public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> people) => people;
public static People GetEnumerator(this People people) => people;
}

结束语

解除原有的限制,扩展方法GetEnumerator支持foreach循环,为特殊的需要提供了一种可能。

如对您有价值,请推荐,您的鼓励是我继续的动力,在此万分感谢。关注本人公众号“码客风云”,享第一时间阅读最新文章。

C# 9.0新特性详解系列之二:扩展方法GetEnumerator支持foreach循环的更多相关文章

  1. C#9.0新特性详解系列之六:增强的模式匹配

    自C#7.0以来,模式匹配就作为C#的一项重要的新特性在不断地演化,这个借鉴于其小弟F#的函数式编程的概念,使得C#的本领越来越多,C#9.0就对模式匹配这一功能做了进一步的增强. 为了更为深入和全面 ...

  2. C# 9.0新特性详解系列之五:记录(record)和with表达式

    1 背景与动机 传统面向对象编程的核心思想是一个对象有着唯一标识,表现为对象引用,封装着随时可变的属性状态,如果你改变了一个属性的状态,这个对象还是原来那个对象,就是对象引用没有因为状态的改变而改变, ...

  3. C# 9.0新特性详解系列之一:只初始化设置器(init only setter)

    1.背景与动机 自C#1.0版本以来,我们要定义一个不可变数据类型的基本做法就是:先声明字段为readonly,再声明只包含get访问器的属性.例子如下: struct Point { public ...

  4. C# 9.0新特性详解系列之三:模块初始化器

    1 背景动机 关于模块或者程序集初始化工作一直是C#的一个痛点,微软内部外部都有大量的报告反应很多客户一直被这个问题困扰,这还不算没有统计上的客户.那么解决这个问题,还有基于什么样的考虑呢? 在库加载 ...

  5. [转]Servlet 3.0 新特性详解

    原文地址:http://blog.csdn.net/xiazdong/article/details/7208316 Servlet 3.0 新特性概览 1.Servlet.Filter.Listen ...

  6. Servlet 3.0 新特性详解

    转自:http://www.ibm.com/developerworks/cn/java/j-lo-servlet30/#major3 Servlet 是 Java EE 规范体系的重要组成部分,也是 ...

  7. 【转帖】Servlet 3.0 新特性详解

    http://www.ibm.com/developerworks/cn/java/j-lo-servlet30/ Servlet 3.0 新特性概述 Servlet 3.0 作为 Java EE 6 ...

  8. Servlet 3.0 新特性详解 (转载)

    原文地址:https://www.ibm.com/developerworks/cn/java/j-lo-servlet30/ Servlet 3.0 新特性概述 Servlet 3.0 作为 Jav ...

  9. Android6.0 新特性详解

    一 运行时权限 Android6.0 引入了一个新的应用权限模型,期望对用户更容易理解,更易用和更安全.该模型将标记为危险的权限从安装时权限(Install Time Permission)模型 移动 ...

随机推荐

  1. windows搭建SVN服务MD版

    windows搭建SVN服务MD 1下载TortoiseSVN 官网下载 根据自己系统环境选择适合的版本 2 安装TortoiseSVN 双击运行程序 出现第一个小坑 原来是你的系统没有打 kb299 ...

  2. day70:Vue:Git&路飞学城页面效果

    目录 1.Git 2.路飞学城项目页面效果 0.安装elements UI 1.顶部导航栏效果 2.轮播图效果 1.Git 什么是git?分布式版本管理工具 1.git操作 # 1 创建git本地仓库 ...

  3. git学习(二) git的文件状态

    git的文件状态 用于查看git的状态 git status 用于git文件的删除操作 git rm 如果只是 git rm --cache 仅删除暂存区里的文件: 如果不加--cache 会删除工作 ...

  4. mybatis-plus自动填充

    1,给字段添加注解 @TableField(value = "create_time", fill = FieldFill.INSERT) 2,添加填充处理器,需要实现接口Meta ...

  5. CVE-2019-15107漏洞复现

    特别说明 漏洞复现参考了teeom sec的panda潘森的文章,是根据他的思路进行复现. 搭建dockers环境, 然后安装vulhub(此处省略,自行百度) 进入vulhub/webmin/CVE ...

  6. day02初识判断和循环

    ​ 新的一天朝气满满,今天小东补充一些昨天没有涉及的新知识! if判断语句 if语句就像是常说的如果....那么,它会做判断,if语句有几种方式: #示例一:判断一是否等于一,等于则输出一等一,否则输 ...

  7. 源码都没调试过,怎么能说熟悉 redis 呢?

    一:背景 1. 讲故事 记得在很久之前给初学的朋友们录制 redis 视频课程,当时结合了不少源码进行解读,自以为讲的还算可以,但还是有一个非常核心的点没被分享到,那就是源码级调试, 对,读源码还远远 ...

  8. 跟我一起学.NetCore之MediatR好像有点火

    前言 随着微服务的流行,而DDD(领域驱动设计)也光速般兴起,CRQS(Command Query Responsibility Seperation--命令查询职责分离).领域事件名词是不是经常在耳 ...

  9. 【Jmeter】Jmeter安装配置教程

    jmeter安装配置教程 1.安装jdk,配置环境变量 进入官网,https://www.oracle.com/downloads/index.html#java,选择 Java (JDK) for ...

  10. 超实用的14个 Spring MVC “隐藏”技巧,用了都说好!

    通常,在Spring MVC中,我们编写一个控制器类来处理来自客户端的请求.然后,控制器调用业务类来处理与业务相关的任务,然后将客户端重定向到逻辑视图名称,该名称由Spring的调度程序Servlet ...