LINQ 標(biāo)準(zhǔn)查詢操作符
更新時(shí)間:2010年02月28日 10:16:48 作者:
本文介紹了LINQ標(biāo)準(zhǔn)查詢操作符。沒(méi)有這些操作符,LINQ就不會(huì)存在。本文為理解這些操作符的功能提供了很好的基礎(chǔ)。了解它們將會(huì)很有幫助,因?yàn)長(zhǎng)INQ的各種Provider都是基于這些操作符來(lái)完成各自豐富的功能。
推薦大家下載本文的PDF進(jìn)行閱讀,可以方便的使用書簽來(lái)閱讀各個(gè)方法,而且代碼中的關(guān)鍵字是高亮顯示的。
pdf版下載地址 http://chabaoo.cn/books/24738.html
一、投影操作符
1. Select
Select操作符對(duì)單個(gè)序列或集合中的值進(jìn)行投影。下面的示例中使用select從序列中返回Employee表的所有列:
using (NorthwindDataContext db=new NorthwindDataContext())
{
//查詢語(yǔ)法
var query =
from e in db.Employees
where e.FirstName.StartsWith("M")
select e;
//方法語(yǔ)法
var q =
db.Employees
.Where(e => e.FirstName.StartsWith("M"))
.Select(e => e);
foreach (var item in query)
{
Console.WriteLine(item.FirstName);
}
}
當(dāng)然,你也可以返回單個(gè)列,例如:
var query =
from e in db.Employees
where e.FirstName.StartsWith("M")
select e.FirstName;
你也可以返回序列中的某幾列,例如:
var query =
from e in db.Employees
where e.FirstName.StartsWith("M")
select new
{
e.FirstName,
e.LastName,
e.Title
};
2. SelectMany
SelectMany操作符提供了將多個(gè)from子句組合起來(lái)的功能,它將每個(gè)對(duì)象的結(jié)果合并成單個(gè)序列。下面是一個(gè)示例:
using (NorthwindDataContext db=new NorthwindDataContext())
{
//查詢語(yǔ)法
var query =
from e in db.Employees
from o in e.Orders
select o;
//方法語(yǔ)法
var q =
db.Employees
.SelectMany(e => e.Orders);
foreach (var item in query)
{
Console.WriteLine(item.Freight);
}
}
二、限制操作符
Where是限制操作符,它將過(guò)濾標(biāo)準(zhǔn)應(yīng)用在序列上,按照提供的邏輯對(duì)序列中的數(shù)據(jù)進(jìn)行過(guò)濾。
Where操作符不啟動(dòng)查詢的執(zhí)行。當(dāng)開(kāi)始對(duì)序列進(jìn)行遍歷時(shí)查詢才開(kāi)始執(zhí)行,此時(shí)過(guò)濾條件將被應(yīng)用到查詢中。Where操作符的使用方法已經(jīng)在第一節(jié)中出現(xiàn)過(guò),這里不再冗述。
三、排序操作符
排序操作符,包括OrderBy、OrderByDescending、ThenBy、ThenByDescending和Reverse,提供了升序或者降序排序。
1. OrderBy
OrderBy操作符將序列中的元素按照升序排列。下面的示例演示了這一點(diǎn):
using (NorthwindDataContext db = new NorthwindDataContext())
{
//查詢語(yǔ)法
var query =
from e in db.Employees
orderby e.FirstName
select e;
//方法語(yǔ)法
var q =
db.Employees
.OrderBy(e => e.FirstName)
.Select(e => e);
foreach (var item in q)
{
Console.WriteLine(item.FirstName);
}
}
這里可以使用OrderBy的重載方法OrderBy(Func<T,TKey>,IComparer<Tkey>)來(lái)指定序列的排序方式。
2. OrderByDescending
OrderByDescending操作符將序列中的元素按照降序排列。用法與OrderBy相同,這里不再演示。
3. ThenBy
ThenBy操作符實(shí)現(xiàn)按照次關(guān)鍵字對(duì)序列進(jìn)行升序排列。此操作符的查詢語(yǔ)法與方法語(yǔ)法略有不同,以下代碼演示了這一點(diǎn):
using (NorthwindDataContext db = new NorthwindDataContext())
{
//查詢語(yǔ)法
var query =
from e in db.Employees
orderby e.FirstName,e.LastName
select e;
//方法語(yǔ)法
var q =
db.Employees
.OrderBy(e => e.FirstName)
.ThenBy(e => e.LastName)
.Select(e => e);
foreach (var item in query)
{
Console.WriteLine(item.FirstName);
}
}
4. ThenByDescending
ThenByDescending操作符實(shí)現(xiàn)按照次關(guān)鍵字對(duì)序列進(jìn)行降序排列。此操作符的查詢語(yǔ)法與方法語(yǔ)法略有不同,以下代碼演示了這一點(diǎn):
using (NorthwindDataContext db = new NorthwindDataContext())
{
//查詢語(yǔ)法
var query =
from e in db.Employees
orderby e.FirstName,e.LastName descending
select e;
//方法語(yǔ)法
var q =
db.Employees
.OrderBy(e => e.FirstName)
.ThenByDescending(e => e.LastName)
.Select(e => e);
foreach (var item in query)
{
Console.WriteLine(item.FirstName);
}
}
5. Reverse
Reverse將會(huì)把序列中的元素按照從后到前的循序反轉(zhuǎn)。需要注意的是,Reverse方法的返回值是void,以下代碼演示了這一點(diǎn):
using (NorthwindDataContext db = new NorthwindDataContext())
{
//方法語(yǔ)法
var q =
db.Employees
.Select(e => e.FirstName)
.ToList();
q.Reverse();
foreach (var item in q)
{
Console.WriteLine(item);
}
}
四、聯(lián)接操作符
聯(lián)接是指將一個(gè)數(shù)據(jù)源對(duì)象與另一個(gè)數(shù)據(jù)源對(duì)象進(jìn)行關(guān)聯(lián)或者聯(lián)合的操作。這兩個(gè)數(shù)據(jù)源對(duì)象通過(guò)一個(gè)共同的值或者屬性進(jìn)行關(guān)聯(lián)。
LINQ有兩個(gè)聯(lián)接操作符:Join和GroupJoin。
1. Join
Join操作符類似于T-SQL中的inner join,它將兩個(gè)數(shù)據(jù)源相聯(lián)接,根據(jù)兩個(gè)數(shù)據(jù)源中相等的值進(jìn)行匹配。例如,可以將產(chǎn)品表與產(chǎn)品類別表相聯(lián)接,得到產(chǎn)品名稱和與其相對(duì)應(yīng)的類別名稱。以下的代碼演示了這一點(diǎn):
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//查詢語(yǔ)法
var query =
from p in db.Products
join c in db.Categories on p.CategoryID equals c.CategoryID
where p.CategoryID == 1
select p;
//方法語(yǔ)法
var q =
db.Products
.Join
(
db.Categories,
p => p.CategoryID,
c => c.CategoryID,
(p, c) => p
)
.Where(p => p.CategoryID == 1);
foreach (var item in query)
{
Console.WriteLine(item.ProductName);
}
}
以上代碼為表述清晰加入了一個(gè)條件“where p.CategoryID == 1”,即僅返回產(chǎn)品類別ID為1的所有產(chǎn)品。
2. GroupJoin
GroupJoin操作符常應(yīng)用于返回“主鍵對(duì)象-外鍵對(duì)象集合”形式的查詢,例如“產(chǎn)品類別-此類別下的所有產(chǎn)品”。以下的代碼演示了這一點(diǎn):
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//查詢語(yǔ)法
var query =
from c in db.Categories
join p in db.Products on c.CategoryID equals p.CategoryID into r
select new
{
c.CategoryName,
Products = r
};
//方法語(yǔ)法
var q =
db.Categories
.GroupJoin
(
db.Products,
c => c.CategoryID,
p => p.CategoryID,
(c, p) => new
{
c.CategoryName,
Products = p
}
);
foreach (var item in query)
{
Console.WriteLine("{0} =>", item.CategoryName);
foreach (var p in item.Products)
{
Console.WriteLine(p.ProductName);
}
Console.WriteLine("----------------------------------------------");
}
}
五、分組操作符
分組是根據(jù)一個(gè)特定的值將序列中的元素進(jìn)行分組。LINQ只包含一個(gè)分組操作符:GroupBy。
下面的示例中使用了產(chǎn)品表,以CategoryID作為分組關(guān)鍵值,按照產(chǎn)品類別對(duì)產(chǎn)品進(jìn)行了分組。
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//查詢語(yǔ)法
var query =
from p in db.Products
group p by p.CategoryID;
//方法語(yǔ)法
var q =
db.Products
.GroupBy(p => p.CategoryID);
foreach (var item in query)
{
Console.WriteLine("{0} =>", item.Key);
foreach (var p in item)
{
Console.WriteLine(p.ProductName);
}
Console.WriteLine("----------------------------------------------");
}
}
執(zhí)行GroupBy得到的序列中包含的元素類型為IGrouping<TKey?, T>,其Key屬性代表了分組時(shí)使用的關(guān)鍵值,遍歷IGrouping<TKey?, T>元素可以讀取到每一個(gè)T類型。在此示例中,對(duì)應(yīng)的元素類型為IGrouping<int?, Products>,其Key屬性即為類別ID,遍歷它可以讀取到每一個(gè)產(chǎn)品對(duì)象。
六、串聯(lián)操作符
串聯(lián)是一個(gè)將兩個(gè)集合聯(lián)接在一起的過(guò)程。在LINQ中,這個(gè)過(guò)程通過(guò)Concat操作符來(lái)實(shí)現(xiàn)。
在下面的示例中,將會(huì)把類別名稱串聯(lián)在產(chǎn)品名稱之后:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//方法語(yǔ)法
var q =
db.Products
.Select(p => p.ProductName)
.Concat
(
db.Categories.Select(c => c.CategoryName)
);
foreach (var item in q)
{
Console.WriteLine(item);
}
}
七、聚合操作符
聚合函數(shù)將在序列上執(zhí)行特定的計(jì)算,并返回單個(gè)值,如計(jì)算給定序列平均值、最大值等。共有7種LINQ聚合查詢操作符:Aggregate、Average、Count、LongCount、Max、Min和Sum。
1. Aggregate
Aggregate操作符對(duì)集合值執(zhí)行自定義聚合運(yùn)算。例如,需要列出所有產(chǎn)品類別清單,每個(gè)類別名稱之間用頓號(hào)連接。以下的代碼演示了這一過(guò)程:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//方法語(yǔ)法
var q =
db.Categories
.Select(c => c.CategoryName)
.ToArray()
.Aggregate((current, next) => String.Format("{0}、{1}", current, next));
Console.WriteLine(q);
}
如果你對(duì)這一過(guò)程有些迷惑,那么請(qǐng)參照以下代碼:
var query =
db.Categories
.Select(c => c.CategoryName)
.ToArray();
string r = String.Empty;
foreach (var item in query)
{
r += "、";
r += item;
}
r = r.Substring(1); //去除第一個(gè)頓號(hào)
Console.WriteLine(r);
2. Average
求集合中元素的平均值,返回值類型double
3. Count
求集合中元素的個(gè)數(shù),返回值類型Int32
4. LongCount
求集合中元素的個(gè)數(shù),返回值類型Int64
5. Max
求集合中元素的最大值
6. Min
求集合中元素的最小值
7. Sum
求集合中元素的和
八、集合操作符
LINQ 中的集合操作符是指根據(jù)相同或不同集合(或集)中是否存在等效元素來(lái)生成結(jié)果集的查詢操作,一共有4種:
使用方式均為“集合1.方法名(集合2)”,返回值為運(yùn)算結(jié)果的集合,這里就不演示了。
九、生成操作符
生成是指創(chuàng)建新的值序列。
1. Empty
Empty操作符返回一個(gè)指定類型的空集合。這里的空不是null,而是元素?cái)?shù)量為0的集合。以下的示例演示了如何創(chuàng)建一個(gè)IEnumerable<int>類型的空集合:
var q = Enumerable.Empty<int>();
Console.WriteLine(q == null);
Console.WriteLine(q.Count());
2. DefaultIfEmpty
DefaultIfEmpty將空集合替換為具有默認(rèn)值的單一實(shí)例集合。執(zhí)行此方法獲得的集合將至少含有一個(gè)元素,這是因?yàn)镈efaultIfEmpty方法需要兩個(gè)參數(shù),第一個(gè)參數(shù)是一個(gè)泛型集合,第二個(gè)參數(shù)是相應(yīng)類型的單個(gè)元素,如果第一個(gè)參數(shù)中不含有任何元素,它將返回第二個(gè)參數(shù)指定的單個(gè)元素。如果你使用了DefaultIfEmpty方法的重載方法DefaultIfEmpty<T>(IEnumerable<T> array),如果指定的array集合為空,那么將返回一個(gè)類型為T,值為null的單個(gè)對(duì)象。以下的代碼演示了這一過(guò)程:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//方法語(yǔ)法
var q =
Enumerable.DefaultIfEmpty
(
db.Employees
.Where(e => e.FirstName.StartsWith("Aaf")) //更改此處的條件可獲得不同的集合
, new Employees() { FirstName = "Sunny D.D" }
);
Console.WriteLine(q.Count());
foreach (var item in q)
{
Console.WriteLine(item.FirstName);
}
}
3. Range
Range操作符用于生成指定范圍內(nèi)的整數(shù)的序列。它需要兩個(gè)參數(shù),第一個(gè)參數(shù)是序列開(kāi)始的整數(shù)值,第二個(gè)參數(shù)是序列中整數(shù)的數(shù)量。下面的示例演示了使用Range操作符來(lái)生成從0到9的整數(shù)序列:
var q =
Enumerable.Range(0, 10);
foreach (var item in q)
{
Console.WriteLine(item);
}
4. Repeat
Repeat操作符用于生成包含一個(gè)重復(fù)值的集合。它需要兩個(gè)參數(shù),第一個(gè)參數(shù)是任意類型的元素,第二個(gè)參數(shù)是生成的序列中所包含此元素的數(shù)量。下面的示例演示了使用Repeat來(lái)生成一個(gè)包含10個(gè)0的序列:
var q =
Enumerable.Repeat(0, 10);
foreach (var item in q)
{
Console.WriteLine(item);
}
十、轉(zhuǎn)換操作符
轉(zhuǎn)換操作符是用來(lái)實(shí)現(xiàn)將輸入對(duì)象的類型轉(zhuǎn)變?yōu)樾蛄械墓δ?。名稱以“As”開(kāi)頭的轉(zhuǎn)換方法可更改源集合的靜態(tài)類型但不枚舉(延遲加載)此源集合。名稱以“To”開(kāi)頭的方法可枚舉(即時(shí)加載)源集合并將項(xiàng)放入相應(yīng)的集合類型。
1. AsEnumerable
所有實(shí)現(xiàn)了IEnumerable<T>接口的類型都可以調(diào)用此方法來(lái)獲取一個(gè)IEnumerable<T>集合。此方法一般僅用于實(shí)現(xiàn)類中的方法與IEnumerable<T>接口方法重名時(shí)。例如,實(shí)現(xiàn)類Test中有一個(gè)Where方法,當(dāng)使用Test對(duì)象調(diào)用Where時(shí),將執(zhí)行Test自身的Where方法過(guò)程。如果要執(zhí)行IEnumerable<T>的Where方法,便可以使用AsEnumerable進(jìn)行進(jìn)行轉(zhuǎn)換后,再調(diào)用Where方法即可。當(dāng)然,將實(shí)現(xiàn)類Test隱式轉(zhuǎn)換為IEnumerable<T>接口,再調(diào)用接口的Where方法也能達(dá)到同樣的效果。以下的代碼演示了這一過(guò)程:
class AsEnumerableTest<T> : List<T>
{
public void Where(Func<T, bool> func)
{
Console.WriteLine("AsEnumerableTest的Where方法");
}
}
public static void AsEnumerable()
{
AsEnumerableTest<int> q = new AsEnumerableTest<int>() { 1,2,3,4 };
q.Where(r => r < 3);
//q.AsEnumerable().Where(r => r < 3);
//IEnumerable<int> i = q;
//i.Where(r => r < 3);
}
2. Cast
Cast<T> 方法通過(guò)提供必要的類型信息,可在IEnumerable(非泛型)的派生對(duì)象上調(diào)用Cast<T> 方法來(lái)獲得一個(gè)IEnumerable<T>對(duì)象。例如,ArrayList 并不實(shí)現(xiàn) IEnumerable<T>,但通過(guò)調(diào)用 ArrayList 對(duì)象上的 Cast<T>(),就可以使用標(biāo)準(zhǔn)查詢運(yùn)算符查詢?cè)撔蛄小?
如果集合中的元素?zé)o法強(qiáng)制轉(zhuǎn)換為 T 類型,則此方法將引發(fā)異常。以下代碼演示了這一過(guò)程:
ArrayList array = new ArrayList();
array.Add("Bob");
array.Add("Jack");
array.Add(1);
foreach (var item in array.Cast<string>())
{
Console.WriteLine(item);
}
運(yùn)行此代碼,可以輸出“Bob”、“Jack”,然后會(huì)報(bào)出一個(gè)異?!盁o(wú)法將int強(qiáng)制轉(zhuǎn)換為string”,這說(shuō)明Cast方法也是延遲執(zhí)行實(shí)現(xiàn)的,只有在枚舉過(guò)程中才將對(duì)象逐個(gè)強(qiáng)制轉(zhuǎn)換為T類型。
3. OfType
OfType <T> 方法通過(guò)提供必要的類型信息,可在IEnumerable(非泛型)的派生對(duì)象上調(diào)用OfType <T> 方法來(lái)獲得一個(gè)IEnumerable<T>對(duì)象。執(zhí)行OfType<T>方法將返回集合中強(qiáng)制轉(zhuǎn)換類型成功的所有元素。也就是說(shuō),OfType<T>方法與Cast<T> 方法的區(qū)別在于,如果集合中的元素在強(qiáng)制轉(zhuǎn)換失敗的時(shí)候會(huì)跳過(guò),而不是拋出異常。
4. ToArray
ToArray 操作符可以在IEnumerable<T> 類型的任何派生對(duì)象上調(diào)用,返回值為T類型的數(shù)組。
5. ToDictionary
ToDictionary操作符根據(jù)指定的鍵選擇器函數(shù),從IEnumerable<T>創(chuàng)建一個(gè)Dictionary<TKey, TValue>。下面的示例中,將查詢到的產(chǎn)品類別集合轉(zhuǎn)換為Dictionary<類別ID,類別名稱>的鍵-值集合:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//方法語(yǔ)法
var q =
db.Categories
.ToDictionary
(
c => c.CategoryID,
c => c.CategoryName
);
foreach (var item in q)
{
Console.WriteLine("{0} - {1}",item.Key,item.Value);
}
}
需要注意的是,如果省略ToDictionary方法的第二個(gè)參數(shù)(值選擇函數(shù)),那么Value將會(huì)保存一個(gè)類別對(duì)象。還有,如果Key為null,或者出現(xiàn)重復(fù)的Key,都將導(dǎo)致拋出異常。
6. ToList
ToList操作符可以在IEnumerable<T> 類型的任何派生對(duì)象上調(diào)用,返回值為L(zhǎng)ist<T>類型的對(duì)象。
7. ToLookup
ToLookup操作符將創(chuàng)建一個(gè) Lookup<TKey, TElement>對(duì)象,這是一個(gè)one-to-many集合,一個(gè)Key可以對(duì)應(yīng)多個(gè)Value。以下的示例以產(chǎn)品表的所有數(shù)據(jù)作為數(shù)據(jù)源,以類別ID作為Key調(diào)用了ToLookup方法,然后遍歷返回的Lookup<TKey, TElement>對(duì)象,輸出了類別ID以及此類別下的所有產(chǎn)品名稱:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//方法語(yǔ)法
var q =
db.Products
.ToLookup
(
p => p.CategoryID,
p => p.ProductName
);
foreach (var item in q)
{
Console.WriteLine(item.Key);
foreach (var p in item)
{
Console.WriteLine(p);
}
}
}
可以看出,ToLookup操作與GroupBy操作很相似,只不過(guò)GroupBy是延遲加載的,而ToLookup是即使加載。
十一、元素操作符
元素操作符將從一個(gè)序列中返回單個(gè)指定的元素。
1. First
First操作將返回序列中的第一個(gè)元素。如果序列中不包含任何元素,則First<T>方法將引發(fā)異常。若要在源序列為空時(shí)返回默認(rèn)值,需要使用FirstOrDefault方法。以下代碼演示了First<T>方法的使用方式:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//無(wú)參
var query =
db.Employees
.First();
//有參
var q =
db.Employees
.First(e => e.FirstName.StartsWith("S"));
Console.WriteLine(q.FirstName);
}
上述代碼中使用了First<T>方法的無(wú)參方式與有參方式。First<T>的有參方式中可以指定一個(gè)條件,操作將返回序列中滿足此條件的第一個(gè)元素。從查詢結(jié)果上看,source.First<T>(條件)方法與source.Where(條件).First<T>()是一樣的,但是需要注意“First<T>(條件)操作將返回序列中滿足此條件的第一個(gè)元素”,這將忽略后面的遍歷操作,效率更高。
2. FirstOrDefault
FirstOrDefault方法將返回序列中的第一個(gè)元素;如果序列中不包含任何元素,則返回默認(rèn)值。它也可以像First方法一樣傳遞一個(gè)條件。需要說(shuō)明的是如果序列中不包含任何元素,返回的默認(rèn)值是個(gè)怎樣的元素。在這之前,先來(lái)看一下FirstOrDefault方法是如何實(shí)現(xiàn)的:
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
if (list.Count > 0)
{
return list[0];
}
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
return enumerator.Current;
}
}
}
return default(TSource);
}
1. 如果調(diào)用FirstOrDefault方法的序列為空,拋出異常
2. 如果序列成功轉(zhuǎn)換為L(zhǎng)ist<T>,并且元素?cái)?shù)量大于0,則返回首個(gè)元素
3. 如果序列沒(méi)有成功轉(zhuǎn)換為L(zhǎng)ist<T>,則嘗試獲取序列的遍歷器,然后再調(diào)用遍歷器的MoveNext方法,如果返回值為true,則返回當(dāng)前的元素。
4. 如果上述操作都沒(méi)有執(zhí)行,則使用default(T)關(guān)鍵字返回類型T的默認(rèn)值
以下給出MSDN中,對(duì)于default(T)關(guān)鍵字的描述:
在泛型類和泛型方法中產(chǎn)生的一個(gè)問(wèn)題是,在預(yù)先未知以下情況時(shí),如何將默認(rèn)值分配給參數(shù)化類型 T:
T 是引用類型還是值類型。
如果 T 為值類型,則它是數(shù)值還是結(jié)構(gòu)。
給定參數(shù)化類型 T 的一個(gè)變量 t,只有當(dāng) T 為引用類型時(shí),語(yǔ)句 t = null 才有效;只有當(dāng) T 為數(shù)值類型而不是結(jié)構(gòu)時(shí),語(yǔ)句 t = 0 才能正常使用。解決方案是使用 default 關(guān)鍵字,此關(guān)鍵字對(duì)于引用類型會(huì)返回 null,對(duì)于數(shù)值類型會(huì)返回零。對(duì)于結(jié)構(gòu),此關(guān)鍵字將返回初始化為零或 null 的每個(gè)結(jié)構(gòu)成員,具體取決于這些結(jié)構(gòu)是值類型還是引用類型。
3. Last
Last方法將返回序列中的最后一個(gè)元素。使用方法參照First。
4. LastOrDefault
LastOrDefault方法將返回序列中的最后一個(gè)元素;如果序列中不包含任何元素,則返回默認(rèn)值。使用方法參照FirstOrDefault。
5. ElementAt
ElementAt方法返回序列中指定索引處的元素。使用方法參照First。需要注意的是如果索引超出范圍會(huì)導(dǎo)致異常。
6. ElementAtOrDefault
ElementAtOrDefault方法將返回序列中指定索引處的元素;如果索引超出范圍,則返回默認(rèn)值。使用方法參照FirstOrDefault。
7. Single
Single方法的無(wú)參形式將從一個(gè)序列中返回單個(gè)元素,如果該序列包含多個(gè)元素,或者沒(méi)有元素?cái)?shù)為0,則會(huì)引發(fā)異常。也就是說(shuō),在序列執(zhí)行Single方法的無(wú)參形式時(shí),必須保證該序列有且僅有一個(gè)元素。
Single方法的有參形式將從一個(gè)序列中返回符合指定條件的唯一元素,如果有多個(gè)元素,或者沒(méi)有元素符合這一條件,則會(huì)引發(fā)異常。以下代碼演示了Single的使用方式:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//方法語(yǔ)法
var q =
db.Employees
.Single();
var query =
db.Employees
.Single(e => e.FirstName.StartsWith("S"));
Console.WriteLine(query.FirstName);
}
8. SingleOrDefault
SingleOrDefault方法的無(wú)參形式將從一個(gè)序列中返回單個(gè)元素。如果元素?cái)?shù)為0,則返回默認(rèn)值。如果該序列包含多個(gè)元素,則會(huì)引發(fā)異常。
SingleOrDefault方法的有參形式將從一個(gè)序列中返回符合指定條件的唯一元素,如果元素?cái)?shù)為0,則返回默認(rèn)值;如果該序列包含多個(gè)元素,則會(huì)引發(fā)異常。SingleOrDefault的使用方式與Single相同。
需要注意的是,Single方法與SingleOrDefault方法都是即時(shí)加載的,在代碼進(jìn)行到方法所在位置時(shí),如果引發(fā)了異常,會(huì)立刻拋出。
十二、相等操作符
如果兩個(gè)序列的對(duì)應(yīng)元素相等且這兩個(gè)序列具有相同數(shù)量的元素,則視這兩個(gè)序列相等。
SequenceEqual方法通過(guò)并行地枚舉兩個(gè)數(shù)據(jù)源并比較相應(yīng)元素來(lái)判斷兩個(gè)序列是否相等。如果兩個(gè)序列完全相等,返回true,否則返回false。以下代碼是SequenceEqual方法的實(shí)現(xiàn)過(guò)程:
public static bool SequenceEqual<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
if (comparer == null)
{
comparer = EqualityComparer<TSource>.Default;
}
if (first == null)
{
throw Error.ArgumentNull("first");
}
if (second == null)
{
throw Error.ArgumentNull("second");
}
using (IEnumerator<TSource> enumerator = first.GetEnumerator())
{
using (IEnumerator<TSource> enumerator2 = second.GetEnumerator())
{
while (enumerator.MoveNext())
{
if (!enumerator2.MoveNext() || !comparer.Equals(enumerator.Current, enumerator2.Current))
{
return false;
}
}
if (enumerator2.MoveNext())
{
return false;
}
}
}
return true;
}
以上代碼的執(zhí)行過(guò)程為:
1. 如果比較器為null,賦值為默認(rèn)值EqualityComparer<TSource>.Default。
2. 如果序列1為null,拋出異常。
3. 如果序列2為null,拋出異常。
4. 遍歷序列1。在此過(guò)程中,如果序列2到達(dá)底端則返回false;如果序列1的當(dāng)前值與序列2的當(dāng)前值不同,則返回false。
5. 序列1遍歷完成后,如果序列2未到達(dá)底端,則返回false。
6. 如果第2-5步都沒(méi)有執(zhí)行,則返回true。
十三、限定操作符
限定符運(yùn)算返回一個(gè) Boolean 值,該值指示序列中是否有一些元素滿足條件或是否所有元素都滿足條件。
下圖描述了兩個(gè)不同源序列上的兩個(gè)不同限定符運(yùn)算。第一個(gè)運(yùn)算詢問(wèn)是否有一個(gè)或多個(gè)元素為字符“A”,結(jié)果為 true。第二個(gè)運(yùn)算詢問(wèn)是否所有元素都為字符“A”,結(jié)果為 true。

1. All
All方法用來(lái)確定是否序列中的所有元素都滿足條件。以下代碼演示了All的用法:
string[] source1 = new string[] { "A", "B", "C", "D", "E", "F" };
string[] source2 = new string[] { "A", "A", "A", "A", "A", "A" };
Console.WriteLine(source1.All(w => w == "A")); //console will print "False"
Console.WriteLine(source2.All(w => w == "A")); //console will print "True"
2. Any
Any方法的無(wú)參方式用來(lái)確定序列是否包含任何元素。如果源序列包含元素,則為 true;否則為 false。
Any方法的有參方式用來(lái)確定序列中是否有元素滿足條件。只要有一個(gè)元素符合指定條件即返回true,如果一個(gè)符合指定條件的元素都沒(méi)有則返回false。以下代碼演示了Any方法有參方式的用法:
string[] source1 = new string[] { "A", "B", "C", "D", "E", "F" };
string[] source2 = new string[] { "A", "A", "A", "A", "A", "A" };
Console.WriteLine(source1.Any(w => w == "A")); //console will print "True"
Console.WriteLine(source2.Any(w => w == "A")); //console will print "True"
3. Contains
Contains方法用來(lái)確定序列是否包含滿足指定條件的元素。如果有返回true,否則返回false。以下代碼使用默認(rèn)的String比較器來(lái)判斷序列中是否含有指定的字符串:
string[] source1 = new string[] { "A", "B", "C", "D", "E", "F" };
Console.WriteLine(source1.Contains("A")); //console will print "True"
Console.WriteLine(source1.Contains("G")); //console will print "False"
如果要對(duì)序列中的元素進(jìn)行自定義比較,需要一個(gè)IEqualityComparer<T>接口的實(shí)現(xiàn)類作為比較器,用于比較序列中的元素。
十四、分區(qū)操作符
LINQ 中的分區(qū)指的是在不重新排列元素的情況下,將輸入序列劃分為兩部分,然后返回其中一個(gè)部分的操作。
下圖顯示對(duì)一個(gè)字符序列執(zhí)行三個(gè)不同的分區(qū)操作的結(jié)果。第一個(gè)操作返回序列中的前三個(gè)元素。第二個(gè)操作跳過(guò)前三個(gè)元素,返回剩余的元素。第三個(gè)操作跳過(guò)序列中的前兩個(gè)元素,返回接下來(lái)的三個(gè)元素。

1. Take
Take(int n)方法將從序列的開(kāi)頭返回?cái)?shù)量為n的連續(xù)元素。以下代碼演示了從一個(gè)序列中返回其前五個(gè)元素:
int[] source = new int[] { 86, 2, 77, 94, 100, 65, 5, 22, 70, 55, 81, 66, 45 };
var q = source.Take(5);
foreach (var item in q)
{
Console.WriteLine(item);
}
上述代碼的運(yùn)行結(jié)果為下圖所示:

2. TakeWhile
TakeWhile方法執(zhí)行時(shí)將逐個(gè)比較序列中的每個(gè)元素是否滿足指定條件,直到碰到不符合指定的條件的元素時(shí),返回前面所有的元素組成的序列。以下代碼演示了這一過(guò)程:
int[] source = new int[] { 86, 2, 77, 94, 100, 65, 5, 22, 70, 55, 81, 66, 45 };
var q = source.TakeWhile(i => i < 100);
foreach (var item in q)
{
Console.WriteLine(item);
}
上述代碼的運(yùn)行結(jié)果為下圖所示:

3. Skip
Skip(int n)方法將跳過(guò)序列開(kāi)頭的n個(gè)元素,然后返回其余的連續(xù)元素。以下代碼演示了從一個(gè)序列中跳過(guò)前五個(gè)元素,然后返回其余的元素組成的序列:
int[] source = new int[] { 86, 2, 77, 94, 100, 65, 5, 22, 70, 55, 81, 66, 45 };
var q = source.Skip(5);
foreach (var item in q)
{
Console.WriteLine(item);
}
上述代碼的運(yùn)行結(jié)果為下圖所示:

4. SkipWhile
SkipWhile方法執(zhí)行時(shí)將逐個(gè)比較序列中的每個(gè)元素是否滿足指定條件,直到碰到不符合指定的條件的元素時(shí),返回其余所有的元素組成的序列。以下代碼演示了這一過(guò)程:
int[] source = new int[] { 86, 2, 77, 94, 100, 65, 5, 22, 70, 55, 81, 66, 45 };
var q = source.SkipWhile(i => i < 100);
foreach (var item in q)
{
Console.WriteLine(item);
}
上述代碼的運(yùn)行結(jié)果為下圖所示:

本文總結(jié)
本文介紹了LINQ標(biāo)準(zhǔn)查詢操作符。沒(méi)有這些操作符,LINQ就不會(huì)存在。本文為理解這些操作符的功能提供了很好的基礎(chǔ)。了解它們將會(huì)很有幫助,因?yàn)長(zhǎng)INQ的各種Provider都是基于這些操作符來(lái)完成各自豐富的功能。
pdf版下載地址 http://chabaoo.cn/books/24738.html
一、投影操作符
1. Select
Select操作符對(duì)單個(gè)序列或集合中的值進(jìn)行投影。下面的示例中使用select從序列中返回Employee表的所有列:
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db=new NorthwindDataContext())
{
//查詢語(yǔ)法
var query =
from e in db.Employees
where e.FirstName.StartsWith("M")
select e;
//方法語(yǔ)法
var q =
db.Employees
.Where(e => e.FirstName.StartsWith("M"))
.Select(e => e);
foreach (var item in query)
{
Console.WriteLine(item.FirstName);
}
}
當(dāng)然,你也可以返回單個(gè)列,例如:
復(fù)制代碼 代碼如下:
var query =
from e in db.Employees
where e.FirstName.StartsWith("M")
select e.FirstName;
你也可以返回序列中的某幾列,例如:
復(fù)制代碼 代碼如下:
var query =
from e in db.Employees
where e.FirstName.StartsWith("M")
select new
{
e.FirstName,
e.LastName,
e.Title
};
2. SelectMany
SelectMany操作符提供了將多個(gè)from子句組合起來(lái)的功能,它將每個(gè)對(duì)象的結(jié)果合并成單個(gè)序列。下面是一個(gè)示例:
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db=new NorthwindDataContext())
{
//查詢語(yǔ)法
var query =
from e in db.Employees
from o in e.Orders
select o;
//方法語(yǔ)法
var q =
db.Employees
.SelectMany(e => e.Orders);
foreach (var item in query)
{
Console.WriteLine(item.Freight);
}
}
二、限制操作符
Where是限制操作符,它將過(guò)濾標(biāo)準(zhǔn)應(yīng)用在序列上,按照提供的邏輯對(duì)序列中的數(shù)據(jù)進(jìn)行過(guò)濾。
Where操作符不啟動(dòng)查詢的執(zhí)行。當(dāng)開(kāi)始對(duì)序列進(jìn)行遍歷時(shí)查詢才開(kāi)始執(zhí)行,此時(shí)過(guò)濾條件將被應(yīng)用到查詢中。Where操作符的使用方法已經(jīng)在第一節(jié)中出現(xiàn)過(guò),這里不再冗述。
三、排序操作符
排序操作符,包括OrderBy、OrderByDescending、ThenBy、ThenByDescending和Reverse,提供了升序或者降序排序。
1. OrderBy
OrderBy操作符將序列中的元素按照升序排列。下面的示例演示了這一點(diǎn):
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
//查詢語(yǔ)法
var query =
from e in db.Employees
orderby e.FirstName
select e;
//方法語(yǔ)法
var q =
db.Employees
.OrderBy(e => e.FirstName)
.Select(e => e);
foreach (var item in q)
{
Console.WriteLine(item.FirstName);
}
}
這里可以使用OrderBy的重載方法OrderBy(Func<T,TKey>,IComparer<Tkey>)來(lái)指定序列的排序方式。
2. OrderByDescending
OrderByDescending操作符將序列中的元素按照降序排列。用法與OrderBy相同,這里不再演示。
3. ThenBy
ThenBy操作符實(shí)現(xiàn)按照次關(guān)鍵字對(duì)序列進(jìn)行升序排列。此操作符的查詢語(yǔ)法與方法語(yǔ)法略有不同,以下代碼演示了這一點(diǎn):
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
//查詢語(yǔ)法
var query =
from e in db.Employees
orderby e.FirstName,e.LastName
select e;
//方法語(yǔ)法
var q =
db.Employees
.OrderBy(e => e.FirstName)
.ThenBy(e => e.LastName)
.Select(e => e);
foreach (var item in query)
{
Console.WriteLine(item.FirstName);
}
}
4. ThenByDescending
ThenByDescending操作符實(shí)現(xiàn)按照次關(guān)鍵字對(duì)序列進(jìn)行降序排列。此操作符的查詢語(yǔ)法與方法語(yǔ)法略有不同,以下代碼演示了這一點(diǎn):
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
//查詢語(yǔ)法
var query =
from e in db.Employees
orderby e.FirstName,e.LastName descending
select e;
//方法語(yǔ)法
var q =
db.Employees
.OrderBy(e => e.FirstName)
.ThenByDescending(e => e.LastName)
.Select(e => e);
foreach (var item in query)
{
Console.WriteLine(item.FirstName);
}
}
5. Reverse
Reverse將會(huì)把序列中的元素按照從后到前的循序反轉(zhuǎn)。需要注意的是,Reverse方法的返回值是void,以下代碼演示了這一點(diǎn):
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
//方法語(yǔ)法
var q =
db.Employees
.Select(e => e.FirstName)
.ToList();
q.Reverse();
foreach (var item in q)
{
Console.WriteLine(item);
}
}
四、聯(lián)接操作符
聯(lián)接是指將一個(gè)數(shù)據(jù)源對(duì)象與另一個(gè)數(shù)據(jù)源對(duì)象進(jìn)行關(guān)聯(lián)或者聯(lián)合的操作。這兩個(gè)數(shù)據(jù)源對(duì)象通過(guò)一個(gè)共同的值或者屬性進(jìn)行關(guān)聯(lián)。
LINQ有兩個(gè)聯(lián)接操作符:Join和GroupJoin。
1. Join
Join操作符類似于T-SQL中的inner join,它將兩個(gè)數(shù)據(jù)源相聯(lián)接,根據(jù)兩個(gè)數(shù)據(jù)源中相等的值進(jìn)行匹配。例如,可以將產(chǎn)品表與產(chǎn)品類別表相聯(lián)接,得到產(chǎn)品名稱和與其相對(duì)應(yīng)的類別名稱。以下的代碼演示了這一點(diǎn):
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//查詢語(yǔ)法
var query =
from p in db.Products
join c in db.Categories on p.CategoryID equals c.CategoryID
where p.CategoryID == 1
select p;
//方法語(yǔ)法
var q =
db.Products
.Join
(
db.Categories,
p => p.CategoryID,
c => c.CategoryID,
(p, c) => p
)
.Where(p => p.CategoryID == 1);
foreach (var item in query)
{
Console.WriteLine(item.ProductName);
}
}
以上代碼為表述清晰加入了一個(gè)條件“where p.CategoryID == 1”,即僅返回產(chǎn)品類別ID為1的所有產(chǎn)品。
2. GroupJoin
GroupJoin操作符常應(yīng)用于返回“主鍵對(duì)象-外鍵對(duì)象集合”形式的查詢,例如“產(chǎn)品類別-此類別下的所有產(chǎn)品”。以下的代碼演示了這一點(diǎn):
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//查詢語(yǔ)法
var query =
from c in db.Categories
join p in db.Products on c.CategoryID equals p.CategoryID into r
select new
{
c.CategoryName,
Products = r
};
//方法語(yǔ)法
var q =
db.Categories
.GroupJoin
(
db.Products,
c => c.CategoryID,
p => p.CategoryID,
(c, p) => new
{
c.CategoryName,
Products = p
}
);
foreach (var item in query)
{
Console.WriteLine("{0} =>", item.CategoryName);
foreach (var p in item.Products)
{
Console.WriteLine(p.ProductName);
}
Console.WriteLine("----------------------------------------------");
}
}
五、分組操作符
分組是根據(jù)一個(gè)特定的值將序列中的元素進(jìn)行分組。LINQ只包含一個(gè)分組操作符:GroupBy。
下面的示例中使用了產(chǎn)品表,以CategoryID作為分組關(guān)鍵值,按照產(chǎn)品類別對(duì)產(chǎn)品進(jìn)行了分組。
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//查詢語(yǔ)法
var query =
from p in db.Products
group p by p.CategoryID;
//方法語(yǔ)法
var q =
db.Products
.GroupBy(p => p.CategoryID);
foreach (var item in query)
{
Console.WriteLine("{0} =>", item.Key);
foreach (var p in item)
{
Console.WriteLine(p.ProductName);
}
Console.WriteLine("----------------------------------------------");
}
}
執(zhí)行GroupBy得到的序列中包含的元素類型為IGrouping<TKey?, T>,其Key屬性代表了分組時(shí)使用的關(guān)鍵值,遍歷IGrouping<TKey?, T>元素可以讀取到每一個(gè)T類型。在此示例中,對(duì)應(yīng)的元素類型為IGrouping<int?, Products>,其Key屬性即為類別ID,遍歷它可以讀取到每一個(gè)產(chǎn)品對(duì)象。
六、串聯(lián)操作符
串聯(lián)是一個(gè)將兩個(gè)集合聯(lián)接在一起的過(guò)程。在LINQ中,這個(gè)過(guò)程通過(guò)Concat操作符來(lái)實(shí)現(xiàn)。
在下面的示例中,將會(huì)把類別名稱串聯(lián)在產(chǎn)品名稱之后:
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//方法語(yǔ)法
var q =
db.Products
.Select(p => p.ProductName)
.Concat
(
db.Categories.Select(c => c.CategoryName)
);
foreach (var item in q)
{
Console.WriteLine(item);
}
}
七、聚合操作符
聚合函數(shù)將在序列上執(zhí)行特定的計(jì)算,并返回單個(gè)值,如計(jì)算給定序列平均值、最大值等。共有7種LINQ聚合查詢操作符:Aggregate、Average、Count、LongCount、Max、Min和Sum。
1. Aggregate
Aggregate操作符對(duì)集合值執(zhí)行自定義聚合運(yùn)算。例如,需要列出所有產(chǎn)品類別清單,每個(gè)類別名稱之間用頓號(hào)連接。以下的代碼演示了這一過(guò)程:
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//方法語(yǔ)法
var q =
db.Categories
.Select(c => c.CategoryName)
.ToArray()
.Aggregate((current, next) => String.Format("{0}、{1}", current, next));
Console.WriteLine(q);
}
如果你對(duì)這一過(guò)程有些迷惑,那么請(qǐng)參照以下代碼:
復(fù)制代碼 代碼如下:
var query =
db.Categories
.Select(c => c.CategoryName)
.ToArray();
string r = String.Empty;
foreach (var item in query)
{
r += "、";
r += item;
}
r = r.Substring(1); //去除第一個(gè)頓號(hào)
Console.WriteLine(r);
2. Average
求集合中元素的平均值,返回值類型double
3. Count
求集合中元素的個(gè)數(shù),返回值類型Int32
4. LongCount
求集合中元素的個(gè)數(shù),返回值類型Int64
5. Max
求集合中元素的最大值
6. Min
求集合中元素的最小值
7. Sum
求集合中元素的和
八、集合操作符
LINQ 中的集合操作符是指根據(jù)相同或不同集合(或集)中是否存在等效元素來(lái)生成結(jié)果集的查詢操作,一共有4種:
方法名 |
說(shuō)明 |
Distinct |
從集合移除重復(fù)值。 |
Except |
返回差集,差集是指位于一個(gè)集合但不位于另一個(gè)集合的元素。 |
Intersect |
返回交集,交集是指同時(shí)出現(xiàn)在兩個(gè)集合中的元素。 |
Union |
返回并集,并集是指位于兩個(gè)集合中任一集合的唯一的元素。 |
九、生成操作符
生成是指創(chuàng)建新的值序列。
1. Empty
Empty操作符返回一個(gè)指定類型的空集合。這里的空不是null,而是元素?cái)?shù)量為0的集合。以下的示例演示了如何創(chuàng)建一個(gè)IEnumerable<int>類型的空集合:
var q = Enumerable.Empty<int>();
Console.WriteLine(q == null);
Console.WriteLine(q.Count());
2. DefaultIfEmpty
DefaultIfEmpty將空集合替換為具有默認(rèn)值的單一實(shí)例集合。執(zhí)行此方法獲得的集合將至少含有一個(gè)元素,這是因?yàn)镈efaultIfEmpty方法需要兩個(gè)參數(shù),第一個(gè)參數(shù)是一個(gè)泛型集合,第二個(gè)參數(shù)是相應(yīng)類型的單個(gè)元素,如果第一個(gè)參數(shù)中不含有任何元素,它將返回第二個(gè)參數(shù)指定的單個(gè)元素。如果你使用了DefaultIfEmpty方法的重載方法DefaultIfEmpty<T>(IEnumerable<T> array),如果指定的array集合為空,那么將返回一個(gè)類型為T,值為null的單個(gè)對(duì)象。以下的代碼演示了這一過(guò)程:
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//方法語(yǔ)法
var q =
Enumerable.DefaultIfEmpty
(
db.Employees
.Where(e => e.FirstName.StartsWith("Aaf")) //更改此處的條件可獲得不同的集合
, new Employees() { FirstName = "Sunny D.D" }
);
Console.WriteLine(q.Count());
foreach (var item in q)
{
Console.WriteLine(item.FirstName);
}
}
3. Range
Range操作符用于生成指定范圍內(nèi)的整數(shù)的序列。它需要兩個(gè)參數(shù),第一個(gè)參數(shù)是序列開(kāi)始的整數(shù)值,第二個(gè)參數(shù)是序列中整數(shù)的數(shù)量。下面的示例演示了使用Range操作符來(lái)生成從0到9的整數(shù)序列:
復(fù)制代碼 代碼如下:
var q =
Enumerable.Range(0, 10);
foreach (var item in q)
{
Console.WriteLine(item);
}
4. Repeat
Repeat操作符用于生成包含一個(gè)重復(fù)值的集合。它需要兩個(gè)參數(shù),第一個(gè)參數(shù)是任意類型的元素,第二個(gè)參數(shù)是生成的序列中所包含此元素的數(shù)量。下面的示例演示了使用Repeat來(lái)生成一個(gè)包含10個(gè)0的序列:
復(fù)制代碼 代碼如下:
var q =
Enumerable.Repeat(0, 10);
foreach (var item in q)
{
Console.WriteLine(item);
}
十、轉(zhuǎn)換操作符
轉(zhuǎn)換操作符是用來(lái)實(shí)現(xiàn)將輸入對(duì)象的類型轉(zhuǎn)變?yōu)樾蛄械墓δ?。名稱以“As”開(kāi)頭的轉(zhuǎn)換方法可更改源集合的靜態(tài)類型但不枚舉(延遲加載)此源集合。名稱以“To”開(kāi)頭的方法可枚舉(即時(shí)加載)源集合并將項(xiàng)放入相應(yīng)的集合類型。
1. AsEnumerable
所有實(shí)現(xiàn)了IEnumerable<T>接口的類型都可以調(diào)用此方法來(lái)獲取一個(gè)IEnumerable<T>集合。此方法一般僅用于實(shí)現(xiàn)類中的方法與IEnumerable<T>接口方法重名時(shí)。例如,實(shí)現(xiàn)類Test中有一個(gè)Where方法,當(dāng)使用Test對(duì)象調(diào)用Where時(shí),將執(zhí)行Test自身的Where方法過(guò)程。如果要執(zhí)行IEnumerable<T>的Where方法,便可以使用AsEnumerable進(jìn)行進(jìn)行轉(zhuǎn)換后,再調(diào)用Where方法即可。當(dāng)然,將實(shí)現(xiàn)類Test隱式轉(zhuǎn)換為IEnumerable<T>接口,再調(diào)用接口的Where方法也能達(dá)到同樣的效果。以下的代碼演示了這一過(guò)程:
復(fù)制代碼 代碼如下:
class AsEnumerableTest<T> : List<T>
{
public void Where(Func<T, bool> func)
{
Console.WriteLine("AsEnumerableTest的Where方法");
}
}
public static void AsEnumerable()
{
AsEnumerableTest<int> q = new AsEnumerableTest<int>() { 1,2,3,4 };
q.Where(r => r < 3);
//q.AsEnumerable().Where(r => r < 3);
//IEnumerable<int> i = q;
//i.Where(r => r < 3);
}
2. Cast
Cast<T> 方法通過(guò)提供必要的類型信息,可在IEnumerable(非泛型)的派生對(duì)象上調(diào)用Cast<T> 方法來(lái)獲得一個(gè)IEnumerable<T>對(duì)象。例如,ArrayList 并不實(shí)現(xiàn) IEnumerable<T>,但通過(guò)調(diào)用 ArrayList 對(duì)象上的 Cast<T>(),就可以使用標(biāo)準(zhǔn)查詢運(yùn)算符查詢?cè)撔蛄小?
如果集合中的元素?zé)o法強(qiáng)制轉(zhuǎn)換為 T 類型,則此方法將引發(fā)異常。以下代碼演示了這一過(guò)程:
復(fù)制代碼 代碼如下:
ArrayList array = new ArrayList();
array.Add("Bob");
array.Add("Jack");
array.Add(1);
foreach (var item in array.Cast<string>())
{
Console.WriteLine(item);
}
運(yùn)行此代碼,可以輸出“Bob”、“Jack”,然后會(huì)報(bào)出一個(gè)異?!盁o(wú)法將int強(qiáng)制轉(zhuǎn)換為string”,這說(shuō)明Cast方法也是延遲執(zhí)行實(shí)現(xiàn)的,只有在枚舉過(guò)程中才將對(duì)象逐個(gè)強(qiáng)制轉(zhuǎn)換為T類型。
3. OfType
OfType <T> 方法通過(guò)提供必要的類型信息,可在IEnumerable(非泛型)的派生對(duì)象上調(diào)用OfType <T> 方法來(lái)獲得一個(gè)IEnumerable<T>對(duì)象。執(zhí)行OfType<T>方法將返回集合中強(qiáng)制轉(zhuǎn)換類型成功的所有元素。也就是說(shuō),OfType<T>方法與Cast<T> 方法的區(qū)別在于,如果集合中的元素在強(qiáng)制轉(zhuǎn)換失敗的時(shí)候會(huì)跳過(guò),而不是拋出異常。
4. ToArray
ToArray 操作符可以在IEnumerable<T> 類型的任何派生對(duì)象上調(diào)用,返回值為T類型的數(shù)組。
5. ToDictionary
ToDictionary操作符根據(jù)指定的鍵選擇器函數(shù),從IEnumerable<T>創(chuàng)建一個(gè)Dictionary<TKey, TValue>。下面的示例中,將查詢到的產(chǎn)品類別集合轉(zhuǎn)換為Dictionary<類別ID,類別名稱>的鍵-值集合:
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//方法語(yǔ)法
var q =
db.Categories
.ToDictionary
(
c => c.CategoryID,
c => c.CategoryName
);
foreach (var item in q)
{
Console.WriteLine("{0} - {1}",item.Key,item.Value);
}
}
需要注意的是,如果省略ToDictionary方法的第二個(gè)參數(shù)(值選擇函數(shù)),那么Value將會(huì)保存一個(gè)類別對(duì)象。還有,如果Key為null,或者出現(xiàn)重復(fù)的Key,都將導(dǎo)致拋出異常。
6. ToList
ToList操作符可以在IEnumerable<T> 類型的任何派生對(duì)象上調(diào)用,返回值為L(zhǎng)ist<T>類型的對(duì)象。
7. ToLookup
ToLookup操作符將創(chuàng)建一個(gè) Lookup<TKey, TElement>對(duì)象,這是一個(gè)one-to-many集合,一個(gè)Key可以對(duì)應(yīng)多個(gè)Value。以下的示例以產(chǎn)品表的所有數(shù)據(jù)作為數(shù)據(jù)源,以類別ID作為Key調(diào)用了ToLookup方法,然后遍歷返回的Lookup<TKey, TElement>對(duì)象,輸出了類別ID以及此類別下的所有產(chǎn)品名稱:
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//方法語(yǔ)法
var q =
db.Products
.ToLookup
(
p => p.CategoryID,
p => p.ProductName
);
foreach (var item in q)
{
Console.WriteLine(item.Key);
foreach (var p in item)
{
Console.WriteLine(p);
}
}
}
可以看出,ToLookup操作與GroupBy操作很相似,只不過(guò)GroupBy是延遲加載的,而ToLookup是即使加載。
十一、元素操作符
元素操作符將從一個(gè)序列中返回單個(gè)指定的元素。
1. First
First操作將返回序列中的第一個(gè)元素。如果序列中不包含任何元素,則First<T>方法將引發(fā)異常。若要在源序列為空時(shí)返回默認(rèn)值,需要使用FirstOrDefault方法。以下代碼演示了First<T>方法的使用方式:
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//無(wú)參
var query =
db.Employees
.First();
//有參
var q =
db.Employees
.First(e => e.FirstName.StartsWith("S"));
Console.WriteLine(q.FirstName);
}
上述代碼中使用了First<T>方法的無(wú)參方式與有參方式。First<T>的有參方式中可以指定一個(gè)條件,操作將返回序列中滿足此條件的第一個(gè)元素。從查詢結(jié)果上看,source.First<T>(條件)方法與source.Where(條件).First<T>()是一樣的,但是需要注意“First<T>(條件)操作將返回序列中滿足此條件的第一個(gè)元素”,這將忽略后面的遍歷操作,效率更高。
2. FirstOrDefault
FirstOrDefault方法將返回序列中的第一個(gè)元素;如果序列中不包含任何元素,則返回默認(rèn)值。它也可以像First方法一樣傳遞一個(gè)條件。需要說(shuō)明的是如果序列中不包含任何元素,返回的默認(rèn)值是個(gè)怎樣的元素。在這之前,先來(lái)看一下FirstOrDefault方法是如何實(shí)現(xiàn)的:
復(fù)制代碼 代碼如下:
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
if (list.Count > 0)
{
return list[0];
}
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
return enumerator.Current;
}
}
}
return default(TSource);
}
1. 如果調(diào)用FirstOrDefault方法的序列為空,拋出異常
2. 如果序列成功轉(zhuǎn)換為L(zhǎng)ist<T>,并且元素?cái)?shù)量大于0,則返回首個(gè)元素
3. 如果序列沒(méi)有成功轉(zhuǎn)換為L(zhǎng)ist<T>,則嘗試獲取序列的遍歷器,然后再調(diào)用遍歷器的MoveNext方法,如果返回值為true,則返回當(dāng)前的元素。
4. 如果上述操作都沒(méi)有執(zhí)行,則使用default(T)關(guān)鍵字返回類型T的默認(rèn)值
以下給出MSDN中,對(duì)于default(T)關(guān)鍵字的描述:
在泛型類和泛型方法中產(chǎn)生的一個(gè)問(wèn)題是,在預(yù)先未知以下情況時(shí),如何將默認(rèn)值分配給參數(shù)化類型 T:
T 是引用類型還是值類型。
如果 T 為值類型,則它是數(shù)值還是結(jié)構(gòu)。
給定參數(shù)化類型 T 的一個(gè)變量 t,只有當(dāng) T 為引用類型時(shí),語(yǔ)句 t = null 才有效;只有當(dāng) T 為數(shù)值類型而不是結(jié)構(gòu)時(shí),語(yǔ)句 t = 0 才能正常使用。解決方案是使用 default 關(guān)鍵字,此關(guān)鍵字對(duì)于引用類型會(huì)返回 null,對(duì)于數(shù)值類型會(huì)返回零。對(duì)于結(jié)構(gòu),此關(guān)鍵字將返回初始化為零或 null 的每個(gè)結(jié)構(gòu)成員,具體取決于這些結(jié)構(gòu)是值類型還是引用類型。
3. Last
Last方法將返回序列中的最后一個(gè)元素。使用方法參照First。
4. LastOrDefault
LastOrDefault方法將返回序列中的最后一個(gè)元素;如果序列中不包含任何元素,則返回默認(rèn)值。使用方法參照FirstOrDefault。
5. ElementAt
ElementAt方法返回序列中指定索引處的元素。使用方法參照First。需要注意的是如果索引超出范圍會(huì)導(dǎo)致異常。
6. ElementAtOrDefault
ElementAtOrDefault方法將返回序列中指定索引處的元素;如果索引超出范圍,則返回默認(rèn)值。使用方法參照FirstOrDefault。
7. Single
Single方法的無(wú)參形式將從一個(gè)序列中返回單個(gè)元素,如果該序列包含多個(gè)元素,或者沒(méi)有元素?cái)?shù)為0,則會(huì)引發(fā)異常。也就是說(shuō),在序列執(zhí)行Single方法的無(wú)參形式時(shí),必須保證該序列有且僅有一個(gè)元素。
Single方法的有參形式將從一個(gè)序列中返回符合指定條件的唯一元素,如果有多個(gè)元素,或者沒(méi)有元素符合這一條件,則會(huì)引發(fā)異常。以下代碼演示了Single的使用方式:
復(fù)制代碼 代碼如下:
using (NorthwindDataContext db = new NorthwindDataContext())
{
db.Log = Console.Out; //將生成的T-SQL語(yǔ)句輸出到控制臺(tái)中
//方法語(yǔ)法
var q =
db.Employees
.Single();
var query =
db.Employees
.Single(e => e.FirstName.StartsWith("S"));
Console.WriteLine(query.FirstName);
}
8. SingleOrDefault
SingleOrDefault方法的無(wú)參形式將從一個(gè)序列中返回單個(gè)元素。如果元素?cái)?shù)為0,則返回默認(rèn)值。如果該序列包含多個(gè)元素,則會(huì)引發(fā)異常。
SingleOrDefault方法的有參形式將從一個(gè)序列中返回符合指定條件的唯一元素,如果元素?cái)?shù)為0,則返回默認(rèn)值;如果該序列包含多個(gè)元素,則會(huì)引發(fā)異常。SingleOrDefault的使用方式與Single相同。
需要注意的是,Single方法與SingleOrDefault方法都是即時(shí)加載的,在代碼進(jìn)行到方法所在位置時(shí),如果引發(fā)了異常,會(huì)立刻拋出。
十二、相等操作符
如果兩個(gè)序列的對(duì)應(yīng)元素相等且這兩個(gè)序列具有相同數(shù)量的元素,則視這兩個(gè)序列相等。
SequenceEqual方法通過(guò)并行地枚舉兩個(gè)數(shù)據(jù)源并比較相應(yīng)元素來(lái)判斷兩個(gè)序列是否相等。如果兩個(gè)序列完全相等,返回true,否則返回false。以下代碼是SequenceEqual方法的實(shí)現(xiàn)過(guò)程:
復(fù)制代碼 代碼如下:
public static bool SequenceEqual<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
if (comparer == null)
{
comparer = EqualityComparer<TSource>.Default;
}
if (first == null)
{
throw Error.ArgumentNull("first");
}
if (second == null)
{
throw Error.ArgumentNull("second");
}
using (IEnumerator<TSource> enumerator = first.GetEnumerator())
{
using (IEnumerator<TSource> enumerator2 = second.GetEnumerator())
{
while (enumerator.MoveNext())
{
if (!enumerator2.MoveNext() || !comparer.Equals(enumerator.Current, enumerator2.Current))
{
return false;
}
}
if (enumerator2.MoveNext())
{
return false;
}
}
}
return true;
}
以上代碼的執(zhí)行過(guò)程為:
1. 如果比較器為null,賦值為默認(rèn)值EqualityComparer<TSource>.Default。
2. 如果序列1為null,拋出異常。
3. 如果序列2為null,拋出異常。
4. 遍歷序列1。在此過(guò)程中,如果序列2到達(dá)底端則返回false;如果序列1的當(dāng)前值與序列2的當(dāng)前值不同,則返回false。
5. 序列1遍歷完成后,如果序列2未到達(dá)底端,則返回false。
6. 如果第2-5步都沒(méi)有執(zhí)行,則返回true。
十三、限定操作符
限定符運(yùn)算返回一個(gè) Boolean 值,該值指示序列中是否有一些元素滿足條件或是否所有元素都滿足條件。
下圖描述了兩個(gè)不同源序列上的兩個(gè)不同限定符運(yùn)算。第一個(gè)運(yùn)算詢問(wèn)是否有一個(gè)或多個(gè)元素為字符“A”,結(jié)果為 true。第二個(gè)運(yùn)算詢問(wèn)是否所有元素都為字符“A”,結(jié)果為 true。

1. All
All方法用來(lái)確定是否序列中的所有元素都滿足條件。以下代碼演示了All的用法:
復(fù)制代碼 代碼如下:
string[] source1 = new string[] { "A", "B", "C", "D", "E", "F" };
string[] source2 = new string[] { "A", "A", "A", "A", "A", "A" };
Console.WriteLine(source1.All(w => w == "A")); //console will print "False"
Console.WriteLine(source2.All(w => w == "A")); //console will print "True"
2. Any
Any方法的無(wú)參方式用來(lái)確定序列是否包含任何元素。如果源序列包含元素,則為 true;否則為 false。
Any方法的有參方式用來(lái)確定序列中是否有元素滿足條件。只要有一個(gè)元素符合指定條件即返回true,如果一個(gè)符合指定條件的元素都沒(méi)有則返回false。以下代碼演示了Any方法有參方式的用法:
復(fù)制代碼 代碼如下:
string[] source1 = new string[] { "A", "B", "C", "D", "E", "F" };
string[] source2 = new string[] { "A", "A", "A", "A", "A", "A" };
Console.WriteLine(source1.Any(w => w == "A")); //console will print "True"
Console.WriteLine(source2.Any(w => w == "A")); //console will print "True"
3. Contains
Contains方法用來(lái)確定序列是否包含滿足指定條件的元素。如果有返回true,否則返回false。以下代碼使用默認(rèn)的String比較器來(lái)判斷序列中是否含有指定的字符串:
復(fù)制代碼 代碼如下:
string[] source1 = new string[] { "A", "B", "C", "D", "E", "F" };
Console.WriteLine(source1.Contains("A")); //console will print "True"
Console.WriteLine(source1.Contains("G")); //console will print "False"
如果要對(duì)序列中的元素進(jìn)行自定義比較,需要一個(gè)IEqualityComparer<T>接口的實(shí)現(xiàn)類作為比較器,用于比較序列中的元素。
十四、分區(qū)操作符
LINQ 中的分區(qū)指的是在不重新排列元素的情況下,將輸入序列劃分為兩部分,然后返回其中一個(gè)部分的操作。
下圖顯示對(duì)一個(gè)字符序列執(zhí)行三個(gè)不同的分區(qū)操作的結(jié)果。第一個(gè)操作返回序列中的前三個(gè)元素。第二個(gè)操作跳過(guò)前三個(gè)元素,返回剩余的元素。第三個(gè)操作跳過(guò)序列中的前兩個(gè)元素,返回接下來(lái)的三個(gè)元素。

1. Take
Take(int n)方法將從序列的開(kāi)頭返回?cái)?shù)量為n的連續(xù)元素。以下代碼演示了從一個(gè)序列中返回其前五個(gè)元素:
復(fù)制代碼 代碼如下:
int[] source = new int[] { 86, 2, 77, 94, 100, 65, 5, 22, 70, 55, 81, 66, 45 };
var q = source.Take(5);
foreach (var item in q)
{
Console.WriteLine(item);
}
上述代碼的運(yùn)行結(jié)果為下圖所示:

2. TakeWhile
TakeWhile方法執(zhí)行時(shí)將逐個(gè)比較序列中的每個(gè)元素是否滿足指定條件,直到碰到不符合指定的條件的元素時(shí),返回前面所有的元素組成的序列。以下代碼演示了這一過(guò)程:
復(fù)制代碼 代碼如下:
int[] source = new int[] { 86, 2, 77, 94, 100, 65, 5, 22, 70, 55, 81, 66, 45 };
var q = source.TakeWhile(i => i < 100);
foreach (var item in q)
{
Console.WriteLine(item);
}
上述代碼的運(yùn)行結(jié)果為下圖所示:

3. Skip
Skip(int n)方法將跳過(guò)序列開(kāi)頭的n個(gè)元素,然后返回其余的連續(xù)元素。以下代碼演示了從一個(gè)序列中跳過(guò)前五個(gè)元素,然后返回其余的元素組成的序列:
復(fù)制代碼 代碼如下:
int[] source = new int[] { 86, 2, 77, 94, 100, 65, 5, 22, 70, 55, 81, 66, 45 };
var q = source.Skip(5);
foreach (var item in q)
{
Console.WriteLine(item);
}
上述代碼的運(yùn)行結(jié)果為下圖所示:

4. SkipWhile
SkipWhile方法執(zhí)行時(shí)將逐個(gè)比較序列中的每個(gè)元素是否滿足指定條件,直到碰到不符合指定的條件的元素時(shí),返回其余所有的元素組成的序列。以下代碼演示了這一過(guò)程:
復(fù)制代碼 代碼如下:
int[] source = new int[] { 86, 2, 77, 94, 100, 65, 5, 22, 70, 55, 81, 66, 45 };
var q = source.SkipWhile(i => i < 100);
foreach (var item in q)
{
Console.WriteLine(item);
}
上述代碼的運(yùn)行結(jié)果為下圖所示:

本文總結(jié)
本文介紹了LINQ標(biāo)準(zhǔn)查詢操作符。沒(méi)有這些操作符,LINQ就不會(huì)存在。本文為理解這些操作符的功能提供了很好的基礎(chǔ)。了解它們將會(huì)很有幫助,因?yàn)長(zhǎng)INQ的各種Provider都是基于這些操作符來(lái)完成各自豐富的功能。
相關(guān)文章
asp.net多選項(xiàng)卡頁(yè)面的創(chuàng)建及使用方法
看了很多朋友還不會(huì)創(chuàng)建多選項(xiàng)卡的頁(yè)面,特地總結(jié)了一下用法,看一遍就會(huì)了,感興趣的朋友可以參考下2013-01-01解析在.net中使用XSLT轉(zhuǎn)換xml文檔的示例詳解
本篇文章是對(duì)在.net中使用XSLT轉(zhuǎn)換xml文檔的示例進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05MVC4制作網(wǎng)站教程第二章 用戶注冊(cè)2.1
這篇文章主要為大家詳細(xì)介紹了MVC4制作網(wǎng)站教程,用戶注冊(cè)功能的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08