我学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 IEnumerableGenerateStrings() { 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 IEnumerableGenerateStrings()//==④ { for (int i = 8; i < 100; i++) { yield return i.ToString(); } }
在遇到第①,②句时,并没有进到 GenerateStrings其中,而是遇到foreach时,依次进到GenerateStrings中,取到返回值后,再判断⑤子句,成立后才会输出。
但我在调试的时候明明没有进到GenerateStrings,他就已经有结果了,不懂。再看一个,我们自己写一个扩展方法,功能同上一个的where子句
public static class MyLinq { //一个扩展方法 public static IEnumerableMyWhere(this IEnumerable 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 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 IEnumerableMyWhere(this IEnumerable source,Func 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 IEnumerableMyWhere (this IEnumerable source, Func predicate) { foreach (T item in source) { if (predicate(item)) yield return item; } }
改完这个,主函数里是不用动的,泛型大法好。会玩的你知道下一步怎么玩了么?
static IEnumerable 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 IEnumerableMySelect(this IEnumerable source,Func selector) { foreach (int item in source) { yield return selector(item); } }
像这样
var sequence4 = GenerateIntegers().MySelect(x => x.ToString());
感受一下,结果是一样的。 那么我们又可以写个泛型扩展方法了 public static IEnumerableMySelect (this IEnumerable source, Func 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); }
借助一个匿名类型,我们可以同时得到索引和内容。
- 代码
- 参考资料