LINQ中的陷阱--TakeWhile&SkipWhile

追憶似水流年發表於2015-11-03

在用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();

        }

 

相關文章