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);
}
借助一個匿名類型,我們可以同時得到索引和內容。