----------------------Windows Phone 7手機開發、.Net培訓、期待與您交流!
----------------------
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);
}
在上一個示例代碼中,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