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);
            }
        }
    }

        static void CastAndOfType()
        {
            ArrayList fruits = new ArrayList(4);
            fruits.Add("Apple");
            fruits.Add("Pear");
            fruits.Add(2);
            fruits.Add(5);

            //下面这句会报错,因为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;
        }

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

时间: 2024-08-21 13:37:30

C#基础之IEnumerable的相关文章

C#基础复习IEnumerable(和 yield return的使用滴呀 )

IEnumerable 真是基础中的基础,然而..... 我们直接来看看这个接口的实现吧: 它是一个公开枚举数,该枚举数支持在非泛型集合上进行简单的迭代.换句话说,对于所有数组的遍历,都来自IEnumerable,那么我们就可以利用这个特性,来定义一个能够遍历xxxxxx的通用方法 先看我们的经典实例1: using System; using System.Collections; using System.Collections.Generic; using System.Linq; usi

七、C# 接口

并非只能通过继承使用多态性,还能通过接口使用它. 和抽象类不同,接口不包含任何实现(方法). 然后和抽象类相似,接口也定义了一系列成员,调用者可以依赖这些成员来支持一个特定的功能. 实现接口的类会使用与被实现的接口相同的签名来定义方法. 通过基类来共享成员签名和实现,但通过接口只是共享成员签名,不共享实现. 接口的一个关键特征就是它既不包含实现,也不包含数据. 字段不能出现在一个接口中. 但是可以使用属性,由于属性不会包含任何实现作为接口声明的一部分,所以它不会引用一个支持字段. C#不允许为接

linq和EF查询的用法和区分

我们做项目时,难免会遇到用的不知道是啥,及把linq和EF搞混了 今天我带领大家梳理下思路: 首先说linq查询,然后介绍EF查询 1.linq查询 当我们使用linq查询时,转到定义会调到Queryable 类,  那么也就是说,这个类封装了linq所有查询的方法,那么我们来研究研究这个类 MSDN上是这样解释的:提供一组用于查询实现 IQueryable<T> 的数据结构的 static(在 Visual Basic 中为 Shared)方法. 命名空间:   System.Linq程序集

linq-IEnumerable,IEnumerator

一:linq基础必备IEnumerable,IEnumerator linq查询必须的集合必须要实现这两个接口. <1> IEnumerable GetEnumerator方法 <2> Current 和MoveNext()属性, Reset方法 二:迭代的基础 1. 因为有了这IEnumerable,IEnumerator接口,我们的集合才可以迭代,可以迭代,我们就可以使用select词法和select扩展方法 2. foreach语法糖 谁知道foreach的MSIL是什么??

java web 开发三剑客 -------电子书

Internet,人们通常称为因特网,是当今世界上覆盖面最大和应用最广泛的网络.根据英语构词法,Internet是Inter + net,Inter-作为前缀在英语中表示“在一起,交互”,由此可知Internet的目的是让各个net交互.所以,Internet实质上是将世界上各个国家.各个网络运营商的多个网络相互连接构成的一个全球范围内的统一网,使各个网络之间能够相互到达.各个国家和运营商构建网络采用的底层技术和实现可能各不相同,但只要采用统一的上层协议(TCP/IP)就可以通过Internet

Entity Framework中IQueryable, IEnumerable, IList的区别

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 使用工具追踪EF生成的SQL 使用Entity Framework等ORM框架的时候,SQL对于使用者来说是透明的,往往很多人也不关心ORM所生成的SQL,然而系统出现性能问题的时候就必须关注生成的SQL以发现问题所在. 使用过Toplink的

c#笔试基础(转载)

技术类面试.笔试题汇总 注:标明*的问题属于选择性掌握的内容,能掌握更好,没掌握也没关系. 下面的参考解答只是帮助大家理解,不用背,面试题.笔试题千变万化,不要梦想着把题覆盖了,下面的题是供大家查漏补缺用的,真正的把这些题搞懂了,才能“以不变应万变”.回答问题的时候能联系做过项目的例子是最好的,有的问题后面我已经补充联系到项目中的对应的案例了. 1.简述 private. protected. public. internal 修饰符的访问权限. private : 私有成员, 在类的内部才可以

C#基础知识篇(二)-----------C#笔记

1.关系运算符(比较运算符) 1.关系运算符有哪些? >,< ==,!= >=,<= 2.关系运算符的作用? 用于比较两个事物之间的关系. 3.什么叫关系表达式? 由关系运算符连接起来的式子叫关系表达式. 注意:所有的关系表达式最终都能计算成一个bool类型的值. 2.逻辑运算符 1.逻辑表达式有哪些? 逻辑与:&& ,逻辑或:||  ,逻辑非:!(又叫取反) 2.逻辑运算 语法:表达式1 逻辑运算符  表达式2 逻辑运算符连接的两个表达式,要最终能求解成一个boo

Entity Framework中使用IEnumerable&lt;T&gt;、IQueryable&lt;T&gt;及IList&lt;T&gt;的区别

1. IEnumerable<T> IEnumerable<T> :对于在内存中集合上运行的方法,返回的可枚举对象将捕获传递到方法的参数.在枚举该对象时,将使用查询运算符的逻辑,并返回查询结果. IEnumerable<T>在.Net2.0引入. IEnumberable使用的是LINQ to Object方式,将AsEnumerable()时对应的所有记录先加载到内存,再在此基础上再执行后面的Query. 本地数据源用IEnumerable<T>,并且查询