在用TakeWhile,SkipWhile設定陷阱之前,我們先來看一看他們的兄弟Take和Skip:
public static IEnumerable<T> Take<T>(IEnumerable<T> source, int count)
public static IEnumerable<T> Skip<T>(IEnumerable<T> source, int count)
這兩個操作符從字面上看就能理解其含義.Take將列舉出source中的前count個元素,返回給客戶端.而Skip 則恰好相反,將跳過source中的前count個元素,列舉其餘元素.LINQ內部實現程式碼十分簡單,不用多分析.不過,他們的兄弟TakeWhile 和SkipWhile確埋下了個陷阱,讓我小小的摔了一跤.現在,我就來重新佈置這個陷阱:
考慮如下的資料來源:
static List<Customer> customers = new List<Customer> { new Customer { CustomerID=1,Name="woody1"}, new Customer { CustomerID=2,Name="woody2"}, new Customer { CustomerID=3,Name="woody3"}, new Customer { CustomerID=4,Name="woody1"} };
在這個資料來源的基礎上,我進行了如下操作:
var cs1 = customers.TakeWhile(c => c.Name == "woody1"); var cs2 = customers.TakeWhile(c => c.Name == "woody2"); var cs3 = customers.SkipWhile(c => c.Name == "woody1"); var cs4 = customers.SkipWhile(c => c.Name == "woody2");
好了.現在,你能猜得出來cs1--cs4這四個IEnumerable<Customer>變數中都儲存著些什麼什麼元素嗎?
正確答案是:
cs1 : woody1(CustomerID=1)
cs2 : 沒有任何元素
cs3 : woody2 , woody3 , woody1(CustomerID=4)
cs4 : woody1(CustomerID=1),woody2,woody3,woody1(CustomerID=4)
Surprise?:)反正我是小小的"驚喜"了一下.OK.研究實現程式碼吧...
TakeWhile在LINQ中實現的思想是:對資料來源進行列舉,從第一個列舉得到的元素開始,呼叫客戶端傳入的predicate( c.Name == ""woodyN"),如果這個predicate委託返回true的話,則將該元素作為Current元素返回給客戶端,並且,繼續進行相同的列舉,判斷操作.但是,一旦predicate返回false的話,MoveNext()方法將會返回false,列舉就此打住,忽略剩下的所有元素.
類似的,SkipWhile也對資料來源進行列舉,從第一個列舉得到的元素開始,呼叫客戶端的predicate,如果返回true,則跳過該元素,繼續進行列舉操作.但是,如果一旦predicate返回為false,則該元素以後的所有元素,都不會再呼叫predicate,而全部列舉給客戶端.
這兩個方法總結為:遍歷時,檢查predicate條件,只要一遇到返回false,就打住,後面的元素不再去檢測,直接返回結果。
(內部實現程式碼很簡單,不再列出)
現在,再回頭看看陷阱的正確答案,是不是跑出來了呢?:)最開始,我一直以為是LINQ的一個BUG,還打算上LINQ論壇報BUG,不過,後來細想 Take,Skip,再詳細閱讀了LINQ的文件後,發現似乎這並不是BUG,這就是這兩個操作符的正確邏輯.不過,起這樣的名字,出這樣的結果,實在讓人覺得困惑啊~
完整的控制檯程式程式碼如下:
class Customer { public int CustomerID { get; set; } public string Name { get; set; } } class Program { static void Main(string[] args) { List<Customer> customers = new List<Customer> { new Customer { CustomerID=1,Name="woody1"}, new Customer { CustomerID=2,Name="woody2"}, new Customer { CustomerID=3,Name="woody3"}, new Customer { CustomerID=4,Name="woody1"} }; var cs1 = customers.TakeWhile(c => c.Name == "woody1"); var cs2 = customers.TakeWhile(c => c.Name == "woody2"); var cs3 = customers.SkipWhile(c => c.Name == "woody1"); var cs4 = customers.SkipWhile(c => c.Name == "woody2"); Console.WriteLine("Result One:TakeWhile(c => c.Name == woody1)"); foreach (var customer in cs1) { Console.WriteLine(customer.CustomerID + ":" + customer.Name); } Console.WriteLine("Result Two:TakeWhile(c => c.Name == woody2)"); foreach (var customer in cs2) { Console.WriteLine(customer.CustomerID + ":" + customer.Name); } Console.WriteLine("Result Three:SkipWhile(c => c.Name == woody1)"); foreach (var customer in cs3) { Console.WriteLine(customer.CustomerID + ":" + customer.Name); } Console.WriteLine("Result Four:SkipWhile(c => c.Name == woody2)"); foreach (var customer in cs4) { Console.WriteLine(customer.CustomerID + ":" + customer.Name); } Console.ReadKey(); }