C#基础之IEnumerable

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[10];
int current = 0;
public void Add(Fruit fruit)
{
fruits[current] = fruit;
current++;
} public IEnumerator GetEnumerator()
{
return new FruitEnumerator(fruits);
}
} public class FruitEnumerator:IEnumerator
{
Fruit[] fruits;
int current = -1;
public FruitEnumerator(Fruit[] fruits)
{
this.fruits = fruits;
} //这里需要做一个判断,因为有可能此时current<0或超出数组长度
public object Current
{
get { return CurrentFruit(); }
}
object CurrentFruit()
{
if (current < 0 || 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=0;
}
} class Program
{
static void Main(string[] args)
{
FruitShop fruitShop = new FruitShop();
Fruit fruitApple = new Fruit("Apple", "10");
Fruit fruitPear = new Fruit("Pear", "12");
Fruit fruitGrape = new Fruit("Grape", "15");
fruitShop.Add(fruitApple);
fruitShop.Add(fruitPear);
fruitShop.Add(fruitGrape); foreach (Fruit f in fruitShop)
{
Console.WriteLine(f.fruitName+": "+f.fruitPrice);
}
}
}
 

 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,我们每天用的foreach你真的懂它吗?

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

  2. 为IEnumerable<T>添加RemoveAll<IEnumerable<T>>扩展方法--高性能篇

    最近写代码,遇到一个问题,微软基于List<T>自带的方法是public bool Remove(T item);,可是有时候我们可能会用到诸如RemoveAll<IEnumerab ...

  3. C# 索引器,实现IEnumerable接口的GetEnumerator()方法

    当自定义类需要实现索引时,可以在类中实现索引器. 用Table作为例子,Table由多个Row组成,Row由多个Cell组成, 我们需要实现自定义的table[0],row[0] 索引器定义格式为 [ ...

  4. Entity Framework中使用IEnumerable<T>、IQueryable<T>及IList<T>的区别

    1. IEnumerable<T> IEnumerable<T> :对于在内存中集合上运行的方法,返回的可枚举对象将捕获传递到方法的参数.在枚举该对象时,将使用查询运算符的逻辑 ...

  5. 你可能不知道的陷阱, IEnumerable接口

    1.  IEnumerable 与  IEnumerator IEnumerable枚举器接口的重要性,说一万句话都不过分.几乎所有集合都实现了这个接口,Linq的核心也依赖于这个万能的接口.C语言的 ...

  6. 通过IEnumerable和IDisposable实现可暂停和取消的任务队列

    一般来说,软件中总会有一些长时间的操作,这类操作包括下载文件,转储数据库,或者处理复杂的运算. 一种处理做法是,在主界面上提示正在操作中,有进度条,其他部分不可用.这里带来很大的问题, 使用者不知道到 ...

  7. 判断IEnumerable<T>集合中是否包含有T对象

    比如,有角色集合中,只有用户创建有角色,才出现“分配”铵钮.反之,隐藏. IEnumerable有一个方法,叫Any:

  8. .NET面试题系列[11] - IEnumerable<T>的派生类

    “你每次都选择合适的数据结构了吗?” - Jeffery Zhao .NET面试题系列目录 ICollection<T>继承IEnumerable<T>.在其基础上,增加了Ad ...

  9. .NET面试题系列[10] - IEnumerable的派生类

    .NET面试题系列目录 IEnumerable分为两个版本:泛型的和非泛型的.IEnumerable只有一个方法GetEnumerator.如果你只需要数据而不打算修改它,不打算为集合插入或删除任何成 ...

  10. .NET面试题系列[9] - IEnumerable

    .NET面试题系列目录 什么是IEnumerable? IEnumerable及IEnumerable的泛型版本IEnumerable<T>是一个接口,它只含有一个方法GetEnumera ...

随机推荐

  1. XML的特殊字符

    XML中共有5个特殊的字符,分别是:&<>“’.如果配置文件中的注入值包括这些特殊字符,就需要进行特别处理.有两种解决方法: 其一,采用本例中的<![CDATA[ ]]> ...

  2. SCSI磁盘标准的架构与文档

    来自scsi标准的官方网站http://t10.org/,具体的文档可以去浏览官方网站. (*) This chart reflects the currently approved SCSI pro ...

  3. Linux系统编程(12)——shell基础

    Shell的作用是解释执行用户的命令,用户输入一条命令,Shell就解释执行一条,这种方式称为交互式(Interactive),Shell还有一种执行命令的方式称为批处理(Batch),用户事先写一个 ...

  4. android系统的图片资源

    使用系统的图片资源的好处有,一个是美工不需要重复的做一份已有的图片了,可以节约不少工时:另一个是能保证我们的应用程序的风格与系统一致. 1.引用方式 在源代码*.Java中可以进入如下方式引用: my ...

  5. swing的第一课

    Swing介绍 Swing API 可扩展 GUI组件,以减轻开发者的生活创造基于JAVA前端/GUI应用.它是建立在AWT API之上,并作为 AWTAPI 的更换,因为它几乎每一个控制对应 AWT ...

  6. HDU5137 How Many Maos Does the Guanxi Worth(枚举+dijkstra)

    How Many Maos Does the Guanxi Worth Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 512000/5 ...

  7. 如何把apache和nginx 加入到系统服务,用service 命令来控制启动、停止

    1 把apache 加入到系统服务,即用service 命令来控制Apache 启动.停止  如果Linux服务器上默认安装了httpd的话(用rpm -qa|grep httpd查看),那你就可以用 ...

  8. man/ls/clock/date/echo笔记

    login:    用户名:用户ID    认证机制:Authentication授权:Authorization审计:Audition (日志) prompt,命令提示符:命令:magic numb ...

  9. Python常用模块 (2) (loging、configparser、json、pickle、subprocess)

    logging 简单应用 将日志打印到屏幕 import logging logging.debug('debug message') logging.info('info message') log ...

  10. 心急的C小加(两种解法)

    心急的C小加 时间限制:1000 ms  |  内存限制:65535 KB 难度:4   描述 C小加有一些木棒,它们的长度和质量都已经知道,需要一个机器处理这些木棒,机器开启的时候需要耗费一个单位的 ...