1.IEnumerable的作用

  在使用Linq查询数据时经常以IEnumerable<T>来作为数据查询返回对象,在使用foreach进行遍历时需要该对象实现IEnumerable接口,这2个功能让我对IEnumerable充满了无穷的好奇。然而在VS中查看IEnumerable的定义时发现它只定义了一个GetEnumerator()方法,关于IEnumerator我知道它依靠MoveNext和Current来达到Foreach的遍历,但是具体是如何将数据进行迭代的,完整的流程是怎样的?这些疑虑我觉得只有亲自实现一次自定义集合foreach才能够解决。为此我需要定义一个集合FruitShop类,它是一个关于Fruit对象的集合,代码如下所示。整个流程是第一次遇到foreach里的fruitShop对象时就会去执行FruitShop中的GetEnumerator方法,接着每次执行in关键字就会去执行MoveNext方法,每次取数据则是调用Current属性。

  写完代码之后突然就觉得其实没什么了,不过在查找资料当中发现IEnumerable接口并不是我们看到的只有一个方法,它还有4个扩展方法。其中Cast<T>()和OfType<T>()这2个方法非常实用,代码如下所示。有时候对于非泛型集合比如ArrayList,它只实现了IEnumerable接口而没有实现IEnumerable<T>接口,因此无法使用标准查询运算。但是标准查询运算如此的方便,.NET肯定不会允许这样的不完美发生,于是为IEnumerable提供了2个扩展方法。只要实现了IEnumerable接口的集合就可以使用这2个方法来进行强制转换。而OfType<T>比Cast<T>更加强大,它除了进行强制转换外还可以实现类型的过滤。从结果中可以看到第一个foreach遇到int类型后由于无法转换而报出InvalidCastException异常,而使用OfType<T>进行转换时则会自动进行类型筛选,遇到int类型的数据将不会转换,所以进行转换时OfType<T>是首选。

 public class Fruit
{
public string fruitName;
public string fruitPrice;
public Fruit(string fruitName, string fruitPrice)
{
this.fruitName = fruitName;
this.fruitPrice = fruitPrice;
}
} class FruitShop:IEnumerable
{
Fruit[] fruits=new Fruit[];
int current = ;
public void Add(Fruit fruit)
{
fruits[current] = fruit;
current++;
} public IEnumerator GetEnumerator()
{
return new FruitEnumerator(fruits);
}
} public class FruitEnumerator:IEnumerator
{
Fruit[] fruits;
int current = -;
public FruitEnumerator(Fruit[] fruits)
{
this.fruits = fruits;
} //这里需要做一个判断,因为有可能此时current<0或超出数组长度
public object Current
{
get { return CurrentFruit(); }
}
object CurrentFruit()
{
if (current < || current > fruits.Length)
return null;
else
return fruits[current];
} public bool MoveNext()
{
current++;
if(current<fruits.Length&&fruits[current]!=null)
return true;
return false;
} public void Reset()
{
current=;
}
} class Program
{
static void Main(string[] args)
{
FruitShop fruitShop = new FruitShop();
Fruit fruitApple = new Fruit("Apple", "");
Fruit fruitPear = new Fruit("Pear", "");
Fruit fruitGrape = new Fruit("Grape", "");
fruitShop.Add(fruitApple);
fruitShop.Add(fruitPear);
fruitShop.Add(fruitGrape); foreach (Fruit f in fruitShop)
{
Console.WriteLine(f.fruitName+": "+f.fruitPrice);
}
}
}
        static void CastAndOfType()
{
ArrayList fruits = new ArrayList();
fruits.Add("Apple");
fruits.Add("Pear");
fruits.Add();
fruits.Add(); //下面这句会报错,因为ArrayList没有实现IEnumerable<T>接口,故无法使用标准查询运算
//IEnumerable<string> s=fruitShop.Select(str => str);
//但是使用Cast<T>和OfType<T>来进行转换 IEnumerable<string> s1 = fruits.Cast<string>();
IEnumerable<string> s2 = fruits.OfType<string>();
//虽然Cast<T>和OfType<T>都可以用来进行转换,但是OfType<T>比Cast<T>更加强大,
//它可以对结果进行筛选,而Cast<T>遇到无法强制转换的则会报错 try
{
foreach (string fruit in s1)
{
Console.WriteLine(fruit);
}
}
catch(InvalidCastException invalid)
{
Console.WriteLine(invalid.Message);
} foreach (string fruit in s2)
{
Console.WriteLine(fruit);
}
}

 2.深入IEnumerable

  不知道阅读此文的你是不是觉得Cast和OfType已经理解的差不多了,但是这个过程的内部是如何转换的不知道你有没有想过。我很好奇Cast<T>是如何将一个IEnumerable转为IEnumerable<T>的,而且这个IEnumerable<T>对象竟然还可以存储集合查询的数据。老方法我将将exe文件放入Reflector中,发现在IL里也是调用了这2个方法,于是我去看了这2个方法的源码,代码如下面第一段代码所示。从源码中可以看到调用Cast<T>与OfType<T>其实最本质也就一句代码 : IEnumerable<string> s3 = fruits as IEnumerable<string>;

  也就是说这2个方法并没有什么神秘感仅仅只是使用了as,而OfType则在代码中还做了一个判断而已。既然这样我也可以自定义我的Cast方法了,借助上面第一段自定义实现foreach代码中的fruitShop集合对象,我使用fruitShop调用Cast<T>方法,发现也ok。我本来以为系统自带类ArrayList中可能与IEnumerable<T>有某种联系,没想到自定义的集合类也可以进行转换。于是在自定义MyCast方法中我尝试不使用Cast<T>来转换,而是直接只使用as来进行转换。然而转换后执行foreach结果立马报错,调试中发现as转换的结果为null!很奇怪转换的结果竟然是null,但一想到Cast<T>中的yield return关键字我突然懂了,关于yield请看这篇随笔yield。源码的每一句代码都是有意义的,这否定了我上面傻傻的以为只是一个as的想法。

  下面是整个流程的总结,当调用Cast<T>或OfType<T>时首先会进行as转换得到一个typedSource字段,它一般为null,我不知道这个地方微软为什么要加这一句,什么情况下as转换的结果不为null呢?这个地方笔者还没有弄明白。在调试过程中typedSource总是为null,那么接下来将会进行source的判断,最后会进行一个按需迭代,每次真正需要的时候再去取Fruit对象。而此时正如上面第一段代码中所写的current=-1,执行foreach前s2也是为null的。所以其实之所以可以转换,正是因为集合类实现了IEnumerable接口中的GetEnumerator方法,这个方法返回了可以迭代的IEnumerator对象。并且,这也解释了为什么IEnumerable<T>也可以存储标准查询的结果集,它其实就是一个状态机,里面维护着T对象的状态迭代,也就是MoveNext方法和Current属性,真正需要用到数据的时候就去取Current属性指向的当前T对象,如果没有地方需要用到T那IEnumerable<T>对象将为null。

 public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)
{
IEnumerable<TResult> typedSource = source as IEnumerable<TResult>;
if (typedSource != null) return typedSource;
if (source == null) throw Error.ArgumentNull("source");
return CastIterator<TResult>(source);
}
static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source)
{
foreach (object obj in source) yield return (TResult)obj;
} public static IEnumerable<TResult> OfType<TResult>(this IEnumerable source)
{
if (source == null) throw Error.ArgumentNull("source");
return OfTypeIterator<TResult>(source);
}
static IEnumerable<TResult> OfTypeIterator<TResult>(IEnumerable source)
{
foreach (object obj in source)
{
if (obj is TResult) yield return (TResult)obj;
}
}
        static void Main(string[] args)
{
FruitShop fruitShop = new FruitShop();
Fruit fruitApple = new Fruit("Apple");
Fruit fruitPear = new Fruit("Pear");
Fruit fruitGrape = new Fruit("Grape");
fruitShop.Add(fruitApple);
fruitShop.Add(fruitPear);
fruitShop.Add(fruitGrape); IEnumerable<Fruit> s1 = fruitShop as IEnumerable<Fruit>;
IEnumerable<Fruit> s2 = MyCast(fruitShop); foreach (Fruit str in s2)
Console.WriteLine(str.fruitName);
} static IEnumerable<Fruit> MyCast(FruitShop fruitShop)
{
IEnumerable<Fruit> typedSource = fruitShop as IEnumerable<Fruit>;
if (typedSource != null)
return typedSource;
//if (fruitShop == null) throw Error.ArgumentNull("source");
return CastIterator<Fruit>(fruitShop);
}
static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source)
{
foreach (object obj in source) yield return (TResult)obj;
}

声明:本文原创发表于博客园,作者为方小白,如有错误欢迎指出 。本文未经作者许可不许转载,否则视为侵权。

C#基础之IEnumerable的更多相关文章

  1. 基础知识---IEnumerable、ICollection、IList、IQueryable

    一.定义 IEnumerable public interface IEnumerable<out T> : IEnumerable ICollection public interfac ...

  2. IEnumerable

    C#基础之IEnumerable 1.IEnumerable的作用 在使用Linq查询数据时经常以IEnumerable<T>来作为数据查询返回对象,在使用foreach进行遍历时需要该对 ...

  3. Linq基础必备

    1.linq基础必备之对象初始化器和匿名类型因果分析   3. 一:对象初始化器 1.就是在new的时候给公共属性赋值的一种方式 2. 在没有初始化器之前的时候,我们是怎么初始化的呢??? 1. 构造 ...

  4. linq和EF查询的用法和区分

    我们做项目时,难免会遇到用的不知道是啥,及把linq和EF搞混了 今天我带领大家梳理下思路: 首先说linq查询,然后介绍EF查询 1.linq查询 当我们使用linq查询时,转到定义会调到Query ...

  5. 七、C# 接口

    并非只能通过继承使用多态性,还能通过接口使用它. 和抽象类不同,接口不包含任何实现(方法). 然后和抽象类相似,接口也定义了一系列成员,调用者可以依赖这些成员来支持一个特定的功能.   实现接口的类会 ...

  6. 初识 MongoDB 和 .NET Core 入门

    昨天搭建完毕 MongoDB 集群 后,开始计划了解 MongoDB ,并引入使用场景,这里介绍一下学习过程中的一些笔记,帮助读者快速了解 MongoDB 并使用 C# 对其进行编码. 浅入 Mong ...

  7. C#基础知识系列九(对IEnumerable和IEnumerator接口的糊涂认识)

    前言 IEnumerable.IEnumerator到现在为止对这两个接口还是不太理解,不理解但是自己总是想着试着要搞明白,毕竟自己用的少,所以在此先记录一下.以备自己日后可以来翻查,同时也希望园子里 ...

  8. 【Unity|C#】基础篇(20)——枚举器与迭代器(IEnumerable/IEnumerator)

    [学习资料] <C#图解教程>(第18章):https://www.cnblogs.com/moonache/p/7687551.html 电子书下载:https://pan.baidu. ...

  9. 先说IEnumerable,我们每天用的foreach你真的懂它吗?

    我们先思考几个问题: 为什么在foreach中不能修改item的值? 要实现foreach需要满足什么条件? 为什么Linq to Object中要返回IEnumerable? 接下来,先开始我们的正 ...

随机推荐

  1. js相对路径相关(比如:js中的路径依赖导入该js文件的路径)

    问题描述: 前几天调用同事的js接口文件,在他自己的html测试页面ok,在我这边调用时出现问题. debug过程中,将该测试html移到其他位置都不行,放到原html测试页面同层次路径下是OK的. ...

  2. spring服务定位器类

    此文章是基于 搭建SpringMVC+Spring+Hibernate平台 功能:通过持有的Spring应用场景ApplicationContext,可在任何地方获取bean. 1. 服务定位器类:S ...

  3. 入门 ARM 汇编(二)—— 寻址方式

    忧愁他整天拉着我的心,像一个琴师操练他的琴:悲哀像是海礁间的飞涛:看他那汹涌,听他那呼号!—— 徐志摩·四行诗一首 ilocker:关注 Android 安全(新手) QQ: 2597294287 立 ...

  4. 学习OpenStack之 (3):Devstack Screen 使用技巧

    Devstack环境中,openstack运行在一个screen中,每个service运行在一个window中.我总结的几个tips: 0. 注意需要使用screen启动用户来进行一下操作 1. 查看 ...

  5. 实现跨云应用——基于DNS的负载均衡

    “公有云可以作为传统IT资源的延展,能帮助客户应对不断变化的需求”——这是我们在向客户介绍公有云产品时经常说的一句话.我们来看一个具体的需求: 某客户有一个web站点,部署在自有的数据中心(on-pr ...

  6. 自学一个月的java了

    不知道这篇博文有不有朋友看到.先自我介绍一下,硕士一年级下学期. 对编程感兴趣,硕士一年级下学期转学计算机专业,目前刚好一个月的时间.接触计算机也是刚好一个月的时间. 学习了java,javaweb. ...

  7. 网络之TCP/IP四层模型

    应用层(应用层.表示层.会话层):FTP.Telnet.DNS.SMTP 传输层(传输层):TCP<传输控制协议> . UDP<用户数据报文协议> 网际互连层(网络层):IP. ...

  8. gnuplot 的安装

    需要同时安装gnuplot和gnuplot-x11才能画出图 sudo apt-get install gnuplot gnuplot-x11 gnuplot not showing the grap ...

  9. h5中websocket

    ajax的数据传输是单向的,客户端和服务端没有连接-----客户端建立连接发送请求----服务器端沿着建立的连接返回相应----断开连接 websocket则是可以在客户端和服务器端进行双向的数据接收 ...

  10. lua 模块与环境

    编写一个模块的最简单方法: -- complex.lua -- 模块实际上是一个表 complex = {} -- 定义模块函数 function complex.add(c1,c2) ... end ...