探讨关于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. JVM专题1: 类和类加载机制

    合集目录 JVM专题1: 类和类加载机制 Java对象的结构 在HotSpot虚拟机中, 对象在内存中存储的布局可以分为3块区域 对象头Header 实例数据Instance Data 对齐填充Pad ...

  2. Elasticsearch (1) 文档操作

    本文介绍如何在Elasticsearch中对文档进行操作. 1.检查Elasticsearch及Kibana运行是否正常 在浏览器输入192.168.6.16:9200,有如下输出则说明Elastic ...

  3. C++ 类对象内存模型分析

    编译环境:Windows10 + VS2015 1.空类占用的内存空间 类占内存空间是只类实例化后占用内存空间的大小,类本身是不会占内存空间的.用sizeof计算类的大小时,实际上是计算该类实例化后对 ...

  4. 解决Vue3使用 Ant Design,出现多个Modal,全是黑屏,导致列表页看不见问题!

    尴尬问题 不报错,但是我看着就难受. 求知路上 请教了下强哥,强哥告诉我可能某个样式属性失效引起(无效),建议我F12看下样式. 接着,我F12狂看元素样式,查了一个小时未果,我真抓狂了. 都想明天问 ...

  5. Java-在数组中遍历出最值

    在操作数组时,经常需要获取数组中元素的最值. 代码 public class Example31{ public static void main(String[] args){ int[] arr= ...

  6. Android中四大组件

    Activity  BroadCast Receiver 广播接收者 Service  服务 Content Provider 内容提供者   四大组件都需要在清单文件里面配置一下

  7. Linux如何查看端口占用情况

    Linux如何查看端口 1.lsof -i:端口号 用于查看某一端口的占用情况,比如查看8000端口使用情况,lsof -i:8000 # lsof -i:8000 COMMAND PID USER ...

  8. Linux Shell中的数组及遍历 转

    转自:http://www.linuxidc.com/Linux/2011-09/42929.htm 在Linux下使用shell的时候,为方便起见,偶尔会用到一下数组.数组的申明方式是: array ...

  9. NSTimer、CADisplayLink、GCD 三种定时器的用法 —— 昉

    在软件开发过程中,我们常常需要在某个时间后执行某个方法,或者是按照某个周期一直执行某个方法.在这个时候,我们就需要用到定时器. 在iOS中有很多方法完成定时器的任务,例如 NSTimer.CADisp ...

  10. python——平时遇到问题记录

    # pyhs2安装 #centos yum install groupinstall 'development tools' yum install python34-devel yum instal ...