黑馬程序員—linq入門學習 linq to object

----------------------Windows Phone 7手機開發、.Net培訓、期待與您交流! ----------------------



 

1.麼是LINQ to Objects?

  

Linq to objects是使用LINQ查詢內存中的數據集合,數據集合都需要實現了 IEnumerable  or IEnumerable<(Of <(T>)>) 接口。比如我們需要對

一個數組進行排序,就可以使用SQL風格的LINQ to Objects進行這個數組進行排序。

在使用LINQ to objects之前,需要對了解幾個概念。IEnumerable<T>泛型接口,允許對實現這個接口的數據集合進行枚舉集合內的元素,當然也包

括.NET2.0之前的非泛型接口。 序列(Sequences),我們對實現了IEnumerable<T>接口的集合稱爲序列。標準查詢符,是LINQ提供的的查詢的符號。

下面是操作符列表:

from, in 定義LINQ查詢表達式結構,從指定數據集合中提取出數據或新的數據集合
where 對從數據集合中的數據進行約束限制
select 從數據集合中選取出數據
join, on, equals, into 通過特定的鍵對數據集進行聯結
orderby,ascending,descending 對提取出來的子集按升序或降序進行排序
group, by 對提取出來的子集按特定的值進行分組

大多數的操作符都是通過IEnumerable<T>的擴展函數的方式以及System.Linq.IEnumerable的靜態函數提供查詢相關功能,但是通過擴展函數的方

式可以方便對序列(Sequences)進行復雜的查詢操作,而不需要每次在調用System.Linq.IEnumerable的靜態函數的時候將數據集合作爲第一個參數傳入,

代碼示例如下。

void Main()
 {
     string[] items={"csharp","cpp","python","perl","java"};
     var lens1= System.Linq.Enumerable.Select(items,n=>n.Length);//靜態函數
      var lens2= items.Select(items,n=>n.Length);//擴展函數
      lens1.Dump("長度列表1");
     lens2.Dump("長度列表2");
 }


 

在.NET2.0之前有很多遺留的集合類,比如ArrayList,Stack,Hashtable等非泛型的集合類, 由於沒有實現IEnumerable<T>接口,因此我們不能直接

使用LINQ對他們進行查詢,而需要通過函數Cast() 或者 OfType()進行轉換爲序列。示例如下:

 void Main()
 {
     ArrayList list=new ArrayList(){"csharp","java","vb"};
     //編譯錯誤: Could not find an implementation of the query pattern for source type 
     //'System.Collections.ArrayList'.  'Select' not found.  Consider explicitly 
     //specifying the type of the range variable 'item'.
     var error=from item in list 
             select item;
     //OK
     var ok=from item in list.Cast<string>()
             select item;
     //OK too
     IEnumerable<string> ok2=list.OfType<string>().Select(n=>n);
 }


 

2.LINQ的延遲查詢

 

 

在上一個示例代碼中,IEnumerable<string> ok2=list.OfType<string>().Select(n=>n); ok2保存是什麼呢?我們常常以爲IEnumerable<T>就是

存了查詢出來的序列結果,其實不然,select函數並沒有把查詢出來的結果返回,而只有在IEnumerable<T>被遍歷列舉的時候纔會真正返回查詢結果集合。

下面我們通過代碼示例來驗證:

void Main()
 {
     string[] items={"csharp","vb","java","cpp","python"};
     IEnumerable<string> result=items.Where(n=>n.Length>4);
     //顯示查詢結果
      Console.WriteLine("--------enumerated-----------");
     foreach(string item in result)
     {
         Console.WriteLine(item);
     }
     items[0]="not exist"; //修改數組的內容
     //再次顯示查詢結果
      Console.WriteLine("--------enumerated again-----------");
     foreach(string item in result)
     {
         Console.WriteLine(item);
     }
 }


結果:
--------enumerated-----------
csharp
python
--------enumerated again-----------
not exist
python

 

我們注意示例代碼,result兩次遍歷發生了變化,因爲我們直接修改了數組的內容。我們可以得出結論:查詢被延遲了,當我們對IEnumerable<T>進行

遍歷列舉的時候,序列中的元素纔會被yield返回。

現在我們來討論下延遲查詢的作用。延遲查詢有什麼有點和什麼缺點呢?

優點:1. 執行過程中減少資源的佔用,提高性能。數據只在使用的時候纔讀取出來,而不用每次查詢都要讀取出來。

        2. 實際保存的是查詢條件和約束,源數據發生變化不會影響查詢結果的準確性。

void Main()
 {
     int[] list={1,2,3,4,5,6,7};
     var result=list.Where(n=>n>4);
     result.Dump("結果是:5,6,7");
     list[6]=0;
     result.Dump("結果是:5,6");
 }


在例子中,我們查詢條件是大於4的所有數字,但是中途數組中的數據發生變化,原來的數字 7 修改爲了 0,因此實際我們的查詢結果應該也會變化。

 

因此延遲查詢可以保證我們一直能夠得到想要的數據集合。

 

缺點:數據可能不一致造成異常。由於我們的查詢結果是在實際遍歷的時候纔會讀取出來,因此查詢條件的異常錯誤也就只會在實際遍歷時候纔會產生,前

        面我們總結的延時查詢的優點2,在某些時候也會成爲我們的錯誤。因此在使用的時候要很仔細。

        下面我們還是用代碼示例來說明這個問題。

void Main()
 {
     int[] list={1,2,3,4,5,6,7};
     var result=list.Select(n=>100/n);
     result.Dump(); //正常
     //..其他代碼
      list[6]=0;
     //..其他代碼 
      result.Dump();//除以0異常
  }


 

那麼我們如何讓查詢不是延遲的呢?方法很簡單,在IEnumerable<T>的擴展函數中,比如ToArray, ToList, ToDictionary, or ToLookup等幾個非延遲

方法可以將查詢結果返回。

void Main()
 {
     int[] list={1,2,3,4,5,6,7};
     var result=list.Select(n=>100/n).ToList();
     result.Dump();//result保存查詢結果
 }

3.擴展函數

 

 

前面我們說到過,IEnumerable<T>和IEnumerable 是通過擴展函數提供查詢操作功能,這裏我們對擴展函數做個簡單說明,如果你已經瞭解了擴展函數,

可以直接跳過.擴展函數允許你直接在已經存在的類型上添加函數,而不用修改原有類型,或者通過繼承的方式.擴展函數是一種特殊的靜態函數,但是是需要通過類

型的實例來調用,下面是擴展函數的格式:

public static IEnumerable<T> Where<T>(
           this IEnumerable<T> source,
           Func<T, int, bool> predicate);


 

上面是IEnumerable<T>的標準操作符where,下面我們舉例實現自己的擴展操作符,擴展string類型增加函數統計指定的字符的數量GetCharCount(),

這裏我們需要注意幾點:

1.推薦定義自己的域名空間,統一管理擴展函數。

2.擴展函數和所在的類都要是靜態的。

3.允許重載現有的函數。

namespace  MyExtension //自定義域名空間
 {
     public static class StringExtension  //靜態類
     {   //統計字符在字符串串中出現的次數
         public static int GetCharCount(this string source, char c) //靜態函數
         {
             return (from item in source where item = c select item).Count();
         }
     }
 }



使用擴展函數的示例如下:

using MyExtension //引用我們擴展域名空間
  namespace ConsoleApplication1
 {
    static  class Program
     {
         static void Main(string[] args)
         {
             string demoString = "this is test string";
             int count= demoString.GetCharCount('i'); //直接調用擴展函數
              Console.WriteLine(x);
         }
     }
 }


 

4.常見查詢操作符使用介紹

 

 

 

在經過了上面對linq to objects的瞭解,在這一節中,我們對常見的操作符進行說明,在這裏會根據操作符的是否屬於延遲操作符進行分類。

 

附註:後面的示例中用到的序列都爲 items,定義如下:

string[] items={"charp","cpp","python","perl"};


1.  Where

作用: 過濾序列,將結果放入新序列中

是否延遲:   Yes

參數重載1:   Func<T,bool> 委託  

參數重載2:   Func<T,int,bool>委託  (int 標識索引index)

var result=items.Where(p=>p.Length==4);
//描述: 返回字符串長度爲4的序列 
var result=item.Where((p,i)=>i==1); 
//i爲索引//描述: 返回索引爲1的字符串


2. Select

作用:對序列元素進行操作,返回新的結果序列(返回序列的類型和原序列類型可以不同)

是否延遲:Yes

參數重載1:Func<T, int, S> selector

參數重載2:Func<T, S> selector

var result=items.Select(p=>p.Length);
//描述: 返回序列中所有元素的長度 
var result=items.Select((p,i)=>p+":"+i)
//描述: 返回序列中所有元素和序號的組合序列

 

3. SelectMany

作用:創建新的一對多關係的序列,將原序列中每個元素進行操作轉換爲新的序列。

是否延遲:Yes

參數重載1:Func<T, IEnumerable<S>> selector

參數重載2:Func<T, int, IEnumerable<S>> selector

var result=items.SelectMany(p=>p.ToArray());
//描述:將序列中string元素轉換爲字符數組,即結果是“cpp”被轉換爲IEnerable<Char> 值爲{ ‘c’,‘p’,‘p’}
 var result=items.SelectMany((p,i)=>i==1?p.ToArray():new char[]{})
//描述: 將序列中序號爲1的元素轉換爲字符數組,其他的元素轉換爲空字符數組

4. Take

作用:從原序列中獲取指定數量的元素集合,返回新的序列。

是否延遲:Yes

參數:int count

var result=items.Take(1);//描述: 返回序列的第一個元素 結果爲"Csharp"

5. TakeWhile

作用:從原序列中yield滿足條件的元素,直到遇到不滿足條件的元素,剩餘的其他元素將被忽略。

是否延遲:Yes

參數重載1:Func<T, bool> predicate

參數重載2:Func<T, int, bool> predicate

string[] s={"1","22","333","4444","555","66","7"};var result= s.TakeWhile(p=>p.Length<4);
 //描述: 返回滿足長度小於4的序列,注意:當遍歷到"4444"的時候不滿足條件yield結束,後面的元素雖然滿足條件但是被忽略
//結果:  1  22  333


 

6. Skip

作用:從原序列中跳過指定數量的元素,返回剩餘元素組成的序列。

是否延遲:Yes

參數:int count

var result= items.Skip(2); 
//描述: 跳過兩個元素返回剩餘的元素組成的序列
//結果: {"python","perl"};


 

7. SkipWhile

作用:從原序列中跳過滿足條件的元素,返回剩餘元素組成的序列。

是否延遲:Yes

參數:Func<T, int, bool> predicate

var result= items.SkipWhile(p=>p.Contains(‘h’)); 
//描述: 跳過元素中有包含字符h的元素,返回其他元素
//結果: {"cpp","perl"};


8. ThenBy ThenByDescending

  作用:在已排序後的序列進行第二種方式排序。

是否延遲:Yes

重載參數1:Func<T, K> Selector

重載參數2:Func<T, K> Selector,IComparer<K> comp

 var result=items.OrderBy(s=>s.Length).ThenBy(s=>s[0]); 
//描述:先按照長度排序,然後按照字符串首字母排序


 

9.Join

作用:通過指定的鍵連接多個序列組合成新序列,和SQL的內連接相似。

是否延遲:Yes

參數:IEnumerable<U> inner,Func<T, K> outerKey,Func<U, K> innerKey,Func<T, U, V> result

void Main()
 {
     string[] items1={"aa","bbb","ccc","dddd"};
     string[] items2={"eee","ff","gggg","h","iii"};
     var r=items1.Join(items2,i=>i.Length,j=>j.Length,(i,j)=>new {result=string.Format("{0}:{1}",i,j)});
     r.Dump();
 }
  //將兩個序列按照長度連接組成新序列




結果:
result
aa:ff
bbb:eee
bbb:iii
ccc:eee
ccc:iii
dddd:gggg

 

 

10.GroupBy

作用:對序列按照指定方式進行分組,生產新的分組序列

是否延遲:Yes

參數:Func<T, K> keySelector

返回值:IEnumerable<IGrouping<K, T>>

void Main()
 {
     string[] items={"1","22","33","444","555","6666"};
     var r1=from item in items
         group item by item.Length into g
         select new {g.Key};
     r1.Dump();
     
     //等同於下面
      var r2= items.GroupBy(item=>item.Length).Select(g=>g.Key);
     r2.Dump();
 }

結果:


11.Union

作用:組合兩個序列爲一個序列,去除重複的元素

是否延遲:Yes

參數:IEnumerable<T> second

void Main()
 {
     string[] items={"1","22","6666"};
     string[] items2={"1","22","33",,"777"};
     items.Union(items2).Dump();
 }
結果:


12.ToArray , ToList

作用:將序列轉換爲數組 或 列表

是否延遲:No

參數:none

void Main()
 {
     string[] items={"1","22","33","444","555","6666"};
     var r= items.Where(n=>n.Length>2).ToArray();
     var r2= items.Where(n=>n.Length>2).ToList();
 }
  //r和r2保存的是實際的數組和列表,而不是查詢條件對象


13.ToDictionary

作用:轉換序列爲泛型字典Dictionary of type <K, T>

是否延遲:No

參數:Func<T, K> keySelector,Func<T, E> elementSelector,IEqualityComparer<K> comparer

void Main()
 {
     string[] items={"1","22","33","444","555","6666"};
     items.ToDictionary(k=>k.Length).Dump();//錯誤:重複鍵
      items.ToDictionary(k=>k,k=>k.Length).Dump();
 }
 結果:
 key value
    1    
   2    
   2 
  3 
   3      
  4

5.後記

 

在上面的例子中只列出了幾個常見,某些比較簡單的操作符比如:Min,Max,Count,ElementAt等從字面意思都可以看出來就沒有做描述。

由於理解不夠深入,存在的問題請指正。

 

----------------------Windows Phone 7手機開發、.Net培訓、期待與您交流! ----------------------

詳細請查看:
http://edu.csdn.net/heima



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章