我学Linq/1

我学Linq

Enumerable Where Select

Linq 读作 link 语言集成查询(Language Integrated Query),让我们可以像操作数据库那样操作内存数据。它有个特点,在使用时执行
如何在使用时获取数据,就要用到一个关键字yield,使用时和return一起出现。

先看个例子

        static void Main(string[] args)
        {
            foreach (var item in GenerateStrings())
            {
                Console.WriteLine(item.ToString());
            }
        }
        static IEnumerable<string> GenerateStrings()
        {
            for (int i = 0; i < 10; i++)
            {
                yield return i.ToString();
            }
        }

在foreach的数据源中,调用这个方法。在输出那设一个断点,单步执行,可以看到,数据源并不是在方法执行完成后再返回到foreach中,而是每调用到下一个数据时,在方法中获得下一个数据。这就是yield return.
这种做法有什么好处?在需要的时候调用,数据只在需要用到的时候调入内存,而不是把整个集合都调入内存,节省存储空间,在这个例子里其实并不能很好的体现好嘛。

        static void Main(string[] args)
        {
            var sequence = GenerateStrings();//==①
            sequence = sequence.Where(x => x.Length < 2);//==②    x => x.Length < 2//==⑤
            foreach (var item in sequence)//==③
            {
                Console.WriteLine(item.ToString());//==⑥
            }
        }
        static IEnumerable<string> GenerateStrings()//==④
        {
            for (int i = 8; i < 100; i++)
            {
                yield return i.ToString();
            }
        }

在遇到第①,②句时,并没有进到 GenerateStrings其中,而是遇到foreach时,依次进到GenerateStrings中,取到返回值后,再判断⑤子句,成立后才会输出。
但我在调试的时候明明没有进到GenerateStrings,他就已经有结果了,不懂。

再看一个,我们自己写一个扩展方法,功能同上一个的where子句

    public static class MyLinq
    {
        //一个扩展方法
        public static IEnumerable<string> MyWhere(this IEnumerable<string> source)
        {
            foreach (string item in source)
            {
                if (item.Length < 2)
                    yield return item;
            }
        }
    }
    class Program
    {

        static void Main(string[] args)
        {
            var sequence = GenerateStrings();
            sequence = sequence.Where(x => x.Length < 2);
            var sequence2 = GenerateStrings();
            sequence2 = sequence2.MyWhere();
            foreach (var item in sequence)
            {
                Console.WriteLine(item.ToString());
            }
            foreach (var item in sequence2)
            {
                Console.WriteLine(item.ToString());
            }
            Console.ReadKey();
        }
        static IEnumerable<string> GenerateStrings()
        {
            for (int i = 8; i < 100; i++)
            {
                yield return i.ToString();
            }
        }

    }

输出都是一样的
8
9
8
9

介绍一下第二个foreach的执行过程
遇到sequence2 = sequence2.MyWhere();时是先不执行的
在foreach到的时候才会执行MyWhere
到MyWhere中的foreach后,需要一个数据源
那么就去执行var sequence2 = GenerateStrings()
依次的获取数据,拿到一个8后,判断长度是否小于2,满足,返回8,输出。
9也是一样
到10的时候,判断不小于2
重复获取数据,判断,直到99,没有了,退出。

下面改一下那个扩展方法

 public static IEnumerable<string> MyWhere(this IEnumerable<string> source,Func<string,bool> predicate)
        {
            foreach (string item in source)
            {
                if (predicate(item))
                    yield return item;
            }
        }

还有这里
sequence2 = sequence2.MyWhere(x=>x.Length<2);

这样以后,我们的扩展方法就更加灵活,功能上和C#提供的Where就一样了。后面是一个委托,用来判断条件,返回一个bool值,在if里判断。
再牛逼一点,把我们的MyWhere变成一个泛型方法

public static IEnumerable<T> MyWhere<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            foreach (T item in source)
            {
                if (predicate(item))
                    yield return item;
            }
        }

改完这个,主函数里是不用动的,泛型大法好。会玩的你知道下一步怎么玩了么?

        static IEnumerable<int> GenerateIntegers()
        {
            for (int i = 8; i < 100; i++)
            {
                yield return i;
            }
        }

写个返回整数的方法咯。
var sequence3 = GenerateIntegers().MyWhere(x => x % 7 == 0);
这个就可以拿到所有7的倍数啦。
但是要注意到,我们刚才那个泛型方法没有任何错误检查机制,所以…

下面是select

select就比较简单了
var sequence4 = GenerateIntegers().Select(x => x.ToString());
var sequence5 = GenerateIntegers().Select(x =>true);
自己感受一下select后面的lambda表达式

如果,我们来重写一下这个select

        public static IEnumerable<string> MySelect(this IEnumerable<int> source,Func<int,string> selector)
        {
            foreach (int item in source)
            {
                yield return selector(item);
            }
        }

像这样
var sequence4 = GenerateIntegers().MySelect(x => x.ToString());
感受一下,结果是一样的。
那么我们又可以写个泛型扩展方法了

        public static IEnumerable<TResult> MySelect<TSource,TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
        {
            foreach (TSource item in source)
            {
                yield return selector(item);
            }
        }

其实select 有一个重载的版本是可以获取索引的

            var sequence6 = GenerateIntegers().Select((x,index)=>new { index, str=x.ToString()+"str" });
            foreach (var s in sequence6)
            {
                Console.WriteLine("{0}=={1}", s.index, s.str);
            }

借助一个匿名类型,我们可以同时得到索引和内容。

posted @ 2016-02-10 14:27  天堂迈舞  阅读(177)  评论(0编辑  收藏  举报