Enumerable Where Select
Linq 讀作 link 語言集成查詢(Language Integrated Query)姐赡,讓我們可以像操作數(shù)據(jù)庫那樣操作內(nèi)存數(shù)據(jù)岳颇。它有個(gè)特點(diǎn)氛濒,在使用時(shí)執(zhí)行萍歉。
如何在使用時(shí)獲取數(shù)據(jù),就要用到一個(gè)關(guān)鍵字yield
,使用時(shí)和return
一起出現(xiàn)添吗。
先看個(gè)例子
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的數(shù)據(jù)源中入桂,調(diào)用這個(gè)方法。在輸出那設(shè)一個(gè)斷點(diǎn)菠赚,單步執(zhí)行脑豹,可以看到,數(shù)據(jù)源并不是在方法執(zhí)行完成后再返回到foreach中衡查,而是每調(diào)用到下一個(gè)數(shù)據(jù)時(shí)瘩欺,在方法中獲得下一個(gè)數(shù)據(jù)。這就是yield return
.
這種做法有什么好處拌牲?在需要的時(shí)候調(diào)用俱饿,數(shù)據(jù)只在需要用到的時(shí)候調(diào)入內(nèi)存,而不是把整個(gè)集合都調(diào)入內(nèi)存塌忽,節(jié)省存儲空間拍埠,在這個(gè)例子里其實(shí)并不能很好的體現(xiàn)好嘛。
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();
}
}
在遇到第①砚婆,②句時(shí)械拍,并沒有進(jìn)到 GenerateStrings其中,而是遇到foreach時(shí)装盯,依次進(jìn)到GenerateStrings中,取到返回值后甲馋,再判斷⑤子句埂奈,成立后才會輸出。
但我在調(diào)試的時(shí)候明明沒有進(jìn)到GenerateStrings定躏,他就已經(jīng)有結(jié)果了账磺,不懂。
再看一個(gè)痊远,我們自己寫一個(gè)擴(kuò)展方法垮抗,功能同上一個(gè)的where子句
public static class MyLinq
{
//一個(gè)擴(kuò)展方法
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
介紹一下第二個(gè)foreach的執(zhí)行過程
遇到sequence2 = sequence2.MyWhere();時(shí)是先不執(zhí)行的
在foreach到的時(shí)候才會執(zhí)行MyWhere
到MyWhere中的foreach后,需要一個(gè)數(shù)據(jù)源
那么就去執(zhí)行var sequence2 = GenerateStrings()
依次的獲取數(shù)據(jù)碧聪,拿到一個(gè)8后冒版,判斷長度是否小于2,滿足逞姿,返回8辞嗡,輸出捆等。
9也是一樣
到10的時(shí)候,判斷不小于2
重復(fù)獲取數(shù)據(jù)续室,判斷栋烤,直到99,沒有了挺狰,退出明郭。
下面改一下那個(gè)擴(kuò)展方法
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);
這樣以后,我們的擴(kuò)展方法就更加靈活丰泊,功能上和C#提供的Where就一樣了达址。后面是一個(gè)委托,用來判斷條件趁耗,返回一個(gè)bool值沉唠,在if里判斷。
再牛逼一點(diǎn)苛败,把我們的MyWhere變成一個(gè)泛型方法
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;
}
}
改完這個(gè)满葛,主函數(shù)里是不用動的,泛型大法好罢屈。會玩的你知道下一步怎么玩了么嘀韧?
static IEnumerable<int> GenerateIntegers()
{
for (int i = 8; i < 100; i++)
{
yield return i;
}
}
寫個(gè)返回整數(shù)的方法咯。
var sequence3 = GenerateIntegers().MyWhere(x => x % 7 == 0);
這個(gè)就可以拿到所有7的倍數(shù)啦缠捌。
但是要注意到锄贷,我們剛才那個(gè)泛型方法沒有任何錯(cuò)誤檢查機(jī)制
,所以…
下面是select
select就比較簡單了
var sequence4 = GenerateIntegers().Select(x => x.ToString());
var sequence5 = GenerateIntegers().Select(x =>true);
自己感受一下select后面的lambda表達(dá)式
如果曼月,我們來重寫一下這個(gè)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());
感受一下谊却,結(jié)果是一樣的。
那么我們又可以寫個(gè)泛型擴(kuò)展方法了
public static IEnumerable<TResult> MySelect<TSource,TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
foreach (TSource item in source)
{
yield return selector(item);
}
}
其實(shí)select 有一個(gè)重載的版本是可以獲取索引的
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);
}
借助一個(gè)匿名類型哑芹,我們可以同時(shí)得到索引和內(nèi)容炎辨。