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.依赖倒置原则:高层 ...
随机推荐
- unity3d录音
using System.Collections; using System.Collections.Generic; using UnityEngine; public class record : ...
- GeoServer课程规划
"凡事豫则立,不豫则废." --西汉·戴圣<礼记·中庸> 为了做好GeoServer课程培训,需要拟定一个课程目录,对整个课程做一个宏观上的规划.有了这个规划,就有了目 ...
- java继承子父类构造函数-子类的实例化过程
1 /* 2 * 子父类中的构造函数的特点. 3 * 在子类构造对象时,发现,访问子类构造函数时,父类也运行了. 4 * 为什么呢? 5 * 原因是:在子类的构造函数中第一行有一个默认的隐式语句.su ...
- Qt中编译器
很多时候,Qt构建项目编译的过程中会报错,大部分报错是因为qt的设置出现问题,很多时候环境配置时要选择合适的编译器,debugger调试器等,这里对一些名词解释,内容对新手很友好,大佬就不用看啦. M ...
- 配置kubectl连接多个kubernetes集群
背景:我们通过会有多个k8s集群,例如集群(cn-k8s)和集群(jp-k8s),那个就需要有一台服务器可以同时访问两个集群,方式:将2个集群的config信息存放到一个文件中,通过使用 kubect ...
- Java 锁 概念介绍
一 Java中的锁是什么? /* * 一 Java锁定义? * 在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制. * 锁旨在强制 ...
- Java当中“+=”和“=+”的区别
"+="会自动类型强制转换! 隐含了一个强制类型转换! 一 string a1 = "9"; int a2 = 10; a1+=a2; a1=a1+a2; 不会 ...
- ApacheCN PythonWeb 译文集 20211110 更新
Django By Example 中文版 1 创建一个博客应用 2 为博客添加高级功能 3 扩展你的博客应用 4 创建一个社交网站 5 分享内容到你的网站 6 跟踪用户动作 7 构建在线商店 8 管 ...
- debian下编译安装redis并加入到systemd启动管理
原文地址: http://blog.duhbb.com/2022/02/09/compile-and-install-redis-debian-and-add-to-systemd/ 欢迎访问我的个人 ...
- bom案例1-div拖拽
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...