zoukankan      html  css  js  c++  java
  • IEnumerable

    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);
                }
            }
        }
    复制代码
     View Code

     

     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#
  • 相关阅读:
    框架-.NET:Spring.Net
    杂项-WebService:WebService
    公司-企业内部创业:企业内部创业
    公司-内部创业:内部创业
    架构-软件系统体系结构-B/S架构:B/S架构
    架构-软件系统体系结构-C/S架构:C/S架构
    android google map v1 v2 v3 参考
    选购好的主机和服务器可以有效提高优化效果
    u盘安装ubuntu10.04 、11.04 server
    IOS学习笔记45--UITableView性能优化
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/5727549.html
Copyright © 2011-2022 走看看