欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linq

程序员文章站 2022-07-04 08:44:12
...

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

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

上一篇: LINQ 初学

下一篇: Linq 查询