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);
}
借助一个匿名类型,我们可以同时得到索引和内容。