探讨关于C#中Foreach的本质

要实现foreach需要满足什么条件?

只要类中实现类中的GetEnumerator()方法、MoveNext()方法、Current属性(俗称鸭子类型)都可以使用foreach进行遍历。

所以只要继承IEnumerable或IEnumerator、集合类、数组 等类都可以使用foreach遍历。

c#不要求实现IEnumerable/IEnumerable来使用foreach迭代数据类型。相反,编译器使用一个称为duck typing的概念;它查找GetEnumerator方法,该方法返回具有Current属性和MoveNext方法的类型

以下案例可以看出:编译器用鸭子类型这一概念。该案例编译器不报错,但是运行时候会出错,运行时进行类型检测,发现cars 类无法转化成IEnumerator接口。

通过4种foreach的用法来了解真面模

List<int> i = new() { 1, 1, 1, 2 };
int[] j = { 1, 1, 1, 2 };
//用法一
foreach (var item in i)
{
// item = 22; 错误的写法 item就是 enumerator.Current属性,该属性是只读的;
Console.Write(item);
}
//用法二
foreach (var item in j)
{
Console.Write(item);
}
//用法三
foreach (var item in new F())
{
Console.Write(item + ", "); // 1, 2, 3, 4, 5,
} class F
{
public IEnumerator<int> GetEnumerator()
{
for (var i = 0; i < 5; ++i)
{
yield return i;
}
}
}

上面代码通过ILspy的反编译IIL代码如下:

具体分析:

1、数组是静态的直接可以通过索引确定。集合类是不确定长度的所以不能通过索引方式。数组继承了IEnumerable接口,该接口要求返回IEnumerator对象。

2、集合类实现了IEnumerator接口

3、迭代返回的对象也现实了IEnumerator接口。

通过以上三种方式的使用可知foreach主要用到类中的GetEnumerator()方法、MoveNext()、Current属性。那么我们是否可以推断出不需要继承IEnumerator接口,只要实现这三个方法的类照样可以使用foreach。

为了验证这个想法,我自定义一个类来验证,代码如下:

使用方法4:

using System.Collections;

CarFactory  cars = new CarFactory();
foreach (var car in cars)
{
Console.WriteLine(((Car)car).Modle);
}
public class CarFactory
{
private Car[] carlist;
int position = -1;
//Create internal array in constructor.
public CarFactory()
{
carlist =new Car[2] { new Car { Modle = "长城" }, new Car { Modle = "红旗" }};
} public bool MoveNext()
{
position++;
return (position < carlist.Length);
}
public CarFactory GetEnumerator()
{
return this;
} public Car Current
{
get { return carlist[position]; }
}
} public class Car
{
public string Modle { get; set; }
} /*输出:
长城
红旗*/

反编译后如下:

[CompilerGenerated]
internal class Program
{
private static void <Main>$(string[] args)
{
CarFactory cars = new CarFactory();
CarFactory enumerator = cars.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
Car car = enumerator.Current;
Console.WriteLine(car.Modle);
}
}
finally
{
IDisposable disposable = enumerator as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
}

案例5、应用扩展方法 注入方法GetEnumerator()

//扩展方法 由于CarFactory类中没有GetEnumerator()方法,只能用扩展方法注入。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace CExtention
{
static class CarsExtention
{
public static CarFactory GetEnumerator(this CarFactory carFactory) => carFactory;
}
} //主类 using System.Collections;
using CExtention;
CarFactory cars = new CarFactory();
foreach (var car in cars)
{
Console.WriteLine(((Car)car).Modle);
}
public class CarFactory
{
private Car[] carlist;
int position = -1;
//Create internal array in constructor.
public CarFactory()
{
carlist =new Car[2] { new Car { Modle = "长城" }, new Car { Modle = "红旗" }};
} public bool MoveNext()
{
position++;
return (position < carlist.Length);
} public Car Current
{
get { return carlist[position]; }
}
} public class Car
{
public string Modle { get; set; }
} /*输出:
长城
红旗*/

通过对以上使用方法的分析我们可以得出

Foreach  只要类中实现类中的GetEnumerator()方法、MoveNext()、Current属性。都可以使用foreach进行遍历。

总结:

c#不要求实现IEnumerable/IEnumerable来使用foreach迭代数据类型。相反,编译器使用一个称为duck typing的概念;它查找GetEnumerator方法,该方法返回具有Current属性和MoveNext方法的类型。Duck typing涉及到按名称搜索,而不是依赖于对该方法的接口或显式方法调用。(“鸭子类型”一词源自将像鸭子一样的鸟视为鸭子的怪诞想法,对象必须仅实现 Quack 方法,无需实现 IDuck 接口。) 如果鸭子类型找不到实现的合适可枚举模式,编译器便会检查集合是否实现接口。

.NET 本质论 - 了解 C# foreach 的内部工作原理和使用 yield 的自定义迭代器

C#foreach 本质( 鸭子类型遍历)的更多相关文章

  1. python 全栈开发,Day21(抽象类,接口类,多态,鸭子类型)

    一.昨日复习 派生方法和派生属性 super 只有在子父类拥有同名方法的时候, 想使用子类的对象调用父类的方法时,才使用super super在类内 : super().方法名(arg1,..) 指名 ...

  2. day25 面向对象之多态和鸭子类型

    1.封装方法 如何封装:给方法名称前面加上双下划线 # ATM 的取款功能 # 1.插入银行卡 2.输入密码 3.选择取款金额 4.取款 class ATM: def __insert_card(se ...

  3. 封装之property,多态,鸭子类型,classmethod与staticmethod

    一.封装之Property prooerty是一种特殊的属性,访问时他会执行一段功能(函数)然后返回 '''BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属 ...

  4. python 接口(抽象) 多态,鸭子类型, 多继承原理(mro)

    抽象类与接口类 接口类 继承有两种用途: 一:继承基类的方法,并且做出自己的改变或者扩展(代码重用) 二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数 ...

  5. python面向对象-封装-property-接口-抽象-鸭子类型-03

    封装 什么是封装: # 将复杂的丑陋的隐私的细节隐藏到内部,对外提供简单的使用接口 或 # 对外隐藏内部实现细节,并提供访问的接口 为什么需要封装 1.为了保证关键数据的安全性 2.对外部隐藏内部的实 ...

  6. 面向对象相关概念与在python中的面向对象知识(魔法方法+反射+元类+鸭子类型)

    面向对象知识 封装 封装的原理是,其成员变量代表对象的属性,方法代表这个对象的动作真正的封装是,经过深入的思考,做出良好的抽象(设计属性时用到),给出“完整且最小”的接口,并使得内部细节可以对外透明( ...

  7. 第7.3节 Python特色的面向对象设计:协议、多态及鸭子类型

    Python是一种多态语言,其表现特征是:对象方法的调用方只管方法是否可调用,不管对象是什么类型,从而屏蔽不同类型对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化. 一.    P ...

  8. 类型检查和鸭子类型 Duck typing in computer programming is an application of the duck test 鸭子测试 鸭子类型 指示编译器将类的类型检查安排在运行时而不是编译时 type checking can be specified to occur at run time rather than compile time.

    Go所提供的面向对象功能十分简洁,但却兼具了类型检查和鸭子类型两者的有点,这是何等优秀的设计啊! Duck typing in computer programming is an applicati ...

  9. 面向对象—多态、鸭子类型(Day21)

    编程原则java具有自己的编程原则和设计模式,不能多继承.python的编程原则:1.开放封闭原则:开放是对扩展是开放的,封闭是对修改是封闭的(已经写完的代码程序是不能修改的).2.依赖倒置原则:高层 ...

随机推荐

  1. 云原生新时代弄潮儿k8s凭什么在容器化方面独树一帜?

    云原生新时代弄潮儿k8s凭什么在容器化方面独树一帜? Kubernetes 可以为做些什么? 在学习一种新技能之前,囧囧建议不要上去先看各种牛叉的实现,我们需要先搞清楚这个技能是什么?学习了之后能为我 ...

  2. 学习JAVAWEB第十六天

    今天做了一个简单的登陆界面,HTML+CSS太不熟悉了,明天还得接着做

  3. Homework_2

    禁 止 吃 瓜 我是小鱼 刚才有个同学问我小鱼发生肾么事了 我说怎么回事? 给我发了一个张截图,我一看! 噢!原来是昨天发布第二次寒假作业了 我大一了啊没有闪 来!偷袭!我三岁的小同志 当时就流眼泪了 ...

  4. 如何在 IDEA 中添加 Maven 项目的 Archetype(解决添加不起作用的问题)

    前言 在 IDEA 中点击新建 Maven 模块,会发现他已经为我们罗列出来了许多的 archetype,但有些时候满足不了我们的需求.下面就来看看如何添加自己的脚手架吧. 实现过程 新建模块 在 I ...

  5. AT2651 [ARC077D] SS

    定义 \(nxt_i\) 表示在字符串 \(S\) 中以 \(i\) 结尾的最长 \(border\). 引理一:若 \(n - nxt_n \mid n\) 则 \(S_{1 \sim n - nx ...

  6. 对于网络请求ajax理解

    先对原生Ajax进行理解: Ajax=异步JS和XML,用于创建快速动态网页的技术 可以使网页实现异步更新.这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新. 工作原理 对于Ajax的 ...

  7. maven的三种项目打包方式----jar,war,pom

    1.pom工程:**用在父级工程或聚合工程中.用来做jar包的版本控制.必须指明这个聚合工程的打包方式为pom 2.war工程:将会打包成war,发布在服务器上的工程.如网站或服务.在SpringBo ...

  8. MyEclipse工程中Java Build Path中的JDK版本和Java Compiler Compiler compliance level的区别

    感谢大佬:https://blog.csdn.net/shan9liang/article/details/17266519 问题起源: 今天再在ESB调用WebService测试,需要在jboss上 ...

  9. 关于unix下cp命令复制权限不够的问题

    例如要将mysql-5.7.17-macos10.12-x86_64.tar.gz 拷贝到/usr/local目录下时,执行cp mysql-5.7.17-macos10.12-x86_64.tar. ...

  10. MySQL 数据库SQL语句——高阶版本2

    MySQL 数据库SQL语句--高阶版本2 实验准备 数据库表配置: mysql -uroot -p show databases; create database train_ticket; use ...