C#foreach 本质( 鸭子类型遍历)
探讨关于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进行遍历。
总结:
.NET 本质论 - 了解 C# foreach 的内部工作原理和使用 yield 的自定义迭代器
C#foreach 本质( 鸭子类型遍历)的更多相关文章
- python 全栈开发,Day21(抽象类,接口类,多态,鸭子类型)
一.昨日复习 派生方法和派生属性 super 只有在子父类拥有同名方法的时候, 想使用子类的对象调用父类的方法时,才使用super super在类内 : super().方法名(arg1,..) 指名 ...
- day25 面向对象之多态和鸭子类型
1.封装方法 如何封装:给方法名称前面加上双下划线 # ATM 的取款功能 # 1.插入银行卡 2.输入密码 3.选择取款金额 4.取款 class ATM: def __insert_card(se ...
- 封装之property,多态,鸭子类型,classmethod与staticmethod
一.封装之Property prooerty是一种特殊的属性,访问时他会执行一段功能(函数)然后返回 '''BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属 ...
- python 接口(抽象) 多态,鸭子类型, 多继承原理(mro)
抽象类与接口类 接口类 继承有两种用途: 一:继承基类的方法,并且做出自己的改变或者扩展(代码重用) 二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数 ...
- python面向对象-封装-property-接口-抽象-鸭子类型-03
封装 什么是封装: # 将复杂的丑陋的隐私的细节隐藏到内部,对外提供简单的使用接口 或 # 对外隐藏内部实现细节,并提供访问的接口 为什么需要封装 1.为了保证关键数据的安全性 2.对外部隐藏内部的实 ...
- 面向对象相关概念与在python中的面向对象知识(魔法方法+反射+元类+鸭子类型)
面向对象知识 封装 封装的原理是,其成员变量代表对象的属性,方法代表这个对象的动作真正的封装是,经过深入的思考,做出良好的抽象(设计属性时用到),给出“完整且最小”的接口,并使得内部细节可以对外透明( ...
- 第7.3节 Python特色的面向对象设计:协议、多态及鸭子类型
Python是一种多态语言,其表现特征是:对象方法的调用方只管方法是否可调用,不管对象是什么类型,从而屏蔽不同类型对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化. 一. P ...
- 类型检查和鸭子类型 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 ...
- 面向对象—多态、鸭子类型(Day21)
编程原则java具有自己的编程原则和设计模式,不能多继承.python的编程原则:1.开放封闭原则:开放是对扩展是开放的,封闭是对修改是封闭的(已经写完的代码程序是不能修改的).2.依赖倒置原则:高层 ...
随机推荐
- 羽夏看Win系统内核——进程线程篇
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...
- 机器学习-逻辑回归与SVM的联系与区别
(搬运工) 逻辑回归(LR)与SVM的联系与区别 LR 和 SVM 都可以处理分类问题,且一般都用于处理线性二分类问题(在改进的情况下可以处理多分类问题,如LR的Softmax回归用在深度学习的多分类 ...
- Unity——日志打印工具
一.日志工具功能 封装Debug类,需要实现功能: 1.控制所有日志是否打印: 2.除了Log,Warning,Error外,给更多日志种类(不同颜色): 3.格式化打印日志: 4.不定参数,自动拼接 ...
- JVM 问题分析思路
1. 前言 工作中有可能遇到 java.lang.OutOfMemoryError: Java heap space 内存溢出异常, 本文提供一些内存溢出的分析及解决问题的思路. 常见异常如下: 20 ...
- 通过Python自带模块SimpleHTTPServer快速共享服务的配置文件
简介 SimpleHTTPServer是Python 2自带的一个模块,是Python的Web服务器,简单小巧,快速启动. 它在Python 3已经合并到http.server模块中. SimpleH ...
- Shell编程之循环语句与echo的用法
Shell编程之循环语句与echo的用法 目录 Shell编程之循环语句与echo的用法 一.echo用法 1. echo常用选项 2. 常用的转义字符 3. 特殊符号%.#的用法 二.循环语句 1. ...
- k8s 通过helm发布应用
什么是helm? Helm 是 Kubernetes 的包管理器.Helm 是查找.分享和使用软件构建 Kubernetes 的最优方式. 在红帽系的Linux中我们使用yum来管理RPM包,类似的, ...
- .NET 云原生架构师训练营(权限系统 代码实现 EntityAccess)--学习笔记
目录 开发任务 代码实现 开发任务 DotNetNB.Security.Core:定义 core,models,Istore:实现 default memory store DotNetNB.Secu ...
- 「codeforces - 1284G」Seollal
给定 \(n\times m\) 的网格图,有些格子有障碍,无障碍且相邻的格子之间连边形成图.保证 \((1, 1)\) 无障碍,保证无障碍格子连通. 将网格图黑白染色,相邻格子颜色不同,\((1, ...
- Solution -「GLR-R2」教材运送
\(\mathcal{Description}\) Link. 给定一棵包含 \(n\) 个点,有点权和边权的树.设当前位置 \(s\)(初始时 \(s=1\)),每次在 \(n\) 个结点内 ...