本文主要介紹LINQ查詢操作符 LINQ查詢為最常用的操作符定義了一個聲明語法。還有許多查詢操作符可用於Enumerable類。 下麵的例子需要用到LINQ基礎(一)(http://www.cnblogs.com/afei-24/p/6841361.html)的一些代碼 1.篩選 LINQ查詢使用w ...
本文主要介紹LINQ查詢操作符
LINQ查詢為最常用的操作符定義了一個聲明語法。還有許多查詢操作符可用於Enumerable類。
下麵的例子需要用到LINQ基礎(一)(http://www.cnblogs.com/afei-24/p/6841361.html)的一些代碼
1.篩選
LINQ查詢使用where子句添加條件表達式來篩選,where子句可以合併多個表達式。
var racers = from r in Formula1.GetChampions() where r.Wins>15 && (r.Country == "Brazil" || r.Country =="Austria") select r; foreach(var r in racers) { Console.WriteLine("{0:A}", r); }
上述LINQ表達式映射為C# LINQ查詢的擴展方法:
var racers = Formula1.GetChampions().Where(r =>where r.Wins>15 &&
(r.Country == "Brazil" || r.Country =="Austria")).Select(r => r);
註意,並不是所以查詢都可以使用LINQ查詢語法,也不是所有的擴展方法都映射到LINQ查詢。高級查詢需要使用擴展方法。
2.用索引篩選
不能使用LINQ查詢的一個例子是Where()方法的重載。在WHere()方法的重載中,可以傳遞第二個參數————索引。索引是篩選器返回的每個結果的計數器。可以在表達式中使用這個索引,執行基於索引的計算:
var racers = Formula1.GetChampions(). Where((r, index) => r.LastName.StartsWith("A") && index % 2 != 0); foreach (var r in racers) { Console.WriteLine("{0:A}", r); }
3.類型篩選
為了進行基於類型的篩選,可以使用OfType()擴展方法。
object[] data = { "one", 2, 3, "four", "five", 6 }; var query = data.OfType<string>(); foreach (var s in query) { Console.WriteLine(s); }
輸出:
one
four
five
從集合僅返回字元串。
4.複合的from子句
如果需要根據對象的成員進行篩選,而該成員本身是一個系列,就可以使用複合from子句。例如,LINQ基礎(一)(http://www.cnblogs.com/afei-24/p/6841361.html)中的Racer類定義了一個屬性Cars,Cars是一個字元串數組。
篩選駕駛Ferrari的所以冠軍:
var ferrariDrivers = from r in Formula1.GetChampions() from c in r.Cars where c == "Ferrari" orderby r.LastName select r.FirstName + " " + r.LastName; foreach (var racer in ferrariDrivers) { Console.WriteLine(racer); }
第一個from子句訪問Formula1.GetChampions()方法返回的Racer對象,第二個from子句訪問Racer類的Cars屬性,以返回所以sting類型的賽車。
C#編譯器把複合的from子句和LINQ查詢轉換為SelectMany()擴展方法。SelectMany()擴展方法可以迭代序列中的序列。
SelectMany()的重載版本:
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TCollection>> collectionSelector,
Func<TSource, TCollection, TResult> resultSelector);
第一個參數是隱式參數,它從Formula1.GetChampions()方法接受Racer對象序列。第二個參數是collectionSelector委托,其中定義了內部序列,是序列的序列,本例子為Cars。第三個參數也是一個委托,為每個Racer對象的Cars屬性的每個元素調用這個委托。
這裡Cars是一個字元串數組,會將每個Racer和每個字元串作為參數,調用這個委托。
var ferrariDrivers = Formula1.GetChampions().SelectMany( c => c.Cars, (r, s) => new { Racer=r,Car =s}).Where( s =>s.Car == "Ferrari").OrderBy( r => r.Racer.LastName).Select(r => r.Racer.FirstName + " " + r.Racer.LastName); foreach (var racer in ferrariDrivers) { Console.WriteLine(racer); }
5.排序
要對序列排序,可以使用前面使用過的orderby.也可以使用orderrby descending子句(降序)。
var racers = (from r in Formula1.GetChampions() orderby r.Country descending select r); foreach (var racer in racers) { Console.WriteLine("{0}: {1}, {2}", racer.Country, racer.LastName, racer.FirstName); }
orderby子句解析為OrderBy()方法,orderby r.Country descending解析為OrderByDescending()方法:
var racers = Formula1.GetChampions().OrderByDescending(r => r.Country).Select(r=>r);
OrderBy()和OrderByDescending()方法返回IOrderEnumerable<TSource>。這個介面派生自IEnumerable<TSource>介面,但包含一個額外的方法CreateOrderEnumerable<TSource>()方法。這個方法用於進一步給序列排序,可以在最後一個參數指定升序還是降序:
// 摘要: // 根據某個鍵對 System.Linq.IOrderedEnumerable<TElement> 的元素執行後續排序。 // // 參數: // keySelector: // 用於提取每個元素的鍵的 System.Func<T,TResult>。 // // comparer: // 用於比較鍵在返回序列中的位置的 System.Collections.Generic.IComparer<T>。 // // descending: // 如果為 true,則對元素進行降序排序;如果為 false,則對元素進行升序排序。 // // 類型參數: // TKey: // keySelector 生成的鍵的類型。 // // 返回結果: // 一個 System.Linq.IOrderedEnumerable<TElement>,其元素按鍵排序。 IOrderedEnumerable<TElement> CreateOrderedEnumerable<TKey>(Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending);
例子:
// Create an array of strings to sort. string[] fruits = { "apricot", "orange", "banana", "mango", "apple", "grape", "strawberry" }; // First sort the strings by their length. IOrderedEnumerable<string> sortedFruits2 = fruits.OrderBy(fruit => fruit.Length); // Secondarily sort the strings alphabetically, using the default comparer. IOrderedEnumerable<string> sortedFruits3 = sortedFruits2.CreateOrderedEnumerable<string>( fruit => fruit, Comparer<string>.Default, false);
使用ThenBy和ThenByDescending()方法進行進一步排序,可以添加任意多個:
var racers = Formula1.GetChampions().OrderByDescending(r => r.Country).ThenByDescending(
r => r.LastName).ThenByDescending(r => r.FirstName).Select(r => r);
6.分組
要根據一個關鍵字值對查詢結果分組,可以使用group子句。
// group r by r.Country into g 根據Country屬性組合所有的賽車手,並定義為一個新的集合g,用於訪問分組的結果信息。 //select子句創建一個帶Country和Count屬性的匿名類型。Country = g.Key Key是r.Country var countries = from r in Formula1.GetChampions() group r by r.Country into g orderby g.Count() descending, g.Key where g.Count() >= 2 select new { Country = g.Key, Count = g.Count() }; foreach (var item in countries) { Console.WriteLine("{0, -10} {1}", item.Country, item.Count); }
輸出:
使用擴展方法執行相同的操作,把group r by r.Country 子句解析為GroupBy()方法。在GroupBy()方法的聲明中,它返回實現了IGrouping<TKey, TSource>介面的枚舉對象。IGrouping<TKey, TSource>介面定義了Key屬性,所以在調用了這個方法後,可以訪問分組的關鍵字:
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector);
使用GroupBy方法:
var countries = Formula1.GetChampions().GroupBy(r => r.Country).OrderByDescending( g => g.Count()).ThenBy(g => g.Key).Where(g => g.Count() >= 2).Select( g=>new { Country = g.Key, Count = g.Count() });
7.對嵌套的對象分組
如果得到的分組的對象需要包含嵌套的序列,就可以改變select子句創建匿名類型。
//返回的對象不僅需要包含國家名和賽車手這兩個屬性,還應包含賽車手集合。 //使用from r1 in g orderby r1.LastName select r1.FirstName + " " + r1.LastName 內部子句 var countries = from r in Formula1.GetChampions() group r by r.Country into g orderby g.Count() descending, g.Key where g.Count() >= 2 select new { Country = g.Key, Count = g.Count(), Racers = from r1 in g orderby r1.LastName select r1.FirstName + " " + r1.LastName }; foreach (var item in countries) { Console.WriteLine("{0, -10} {1}", item.Country, item.Count); foreach (var name in item.Racers) { Console.Write("{0}; ", name); } Console.WriteLine(); }
8.內連接
使用join子句可以根據特定的條件合併兩個數據源,但之前要獲得兩個連接的列表。
使用了LINQ基礎(一)(http://www.cnblogs.com/afei-24/p/6841361.html)的代碼
//GetChampions獲得冠軍賽車手 var racers = from r in Formula1.GetChampions() from y in r.Years select new { Year = y, Name = r.FirstName + " " + r.LastName }; //GetContructorChampions獲取冠軍車隊 var teams = from t in Formula1.GetContructorChampions() from y in t.Years select new { Year = y, Name = t.Name }; //得到每一年獲得冠軍的賽車手和車隊 //通過join t in teams on r.Year equals t.Year into rt 子句連接兩個數據源 var racersAndTeams = (from r in racers join t in teams on r.Year equals t.Year into rt from t in rt.DefaultIfEmpty() orderby r.Year select new { Year = r.Year, Champion = r.Name, Constructor = t == null ? "no constructor championship" : t.Name }); Console.WriteLine("Year Champion\t\t Constructor Title"); foreach (var item in racersAndTeams) { Console.WriteLine("{0}: {1,-20} {2}", item.Year, item.Champion, item.Constructor); }
9.左連接
使用內連接返回匹配r.Year equals t.Year的結果。左連接返回左邊數據源的全部元素,即使在右邊的數據源中沒有匹配的元素。
var racers = from r in Formula1.GetChampions() from y in r.Years select new { Year = y, Name = r.FirstName + " " + r.LastName }; var teams = from t in Formula1.GetContructorChampions() from y in t.Years select new { Year = y, Name = t.Name }; //左連接用join和DefaultIfEmpty方法定義。 //如果查詢到左側數據源沒有和右邊數據源Year相同的結果,使用DefaultIfEmpty方法定義右側的預設值(為空) var racersAndTeams = (from r in racers join t in teams on r.Year equals t.Year into rt from t in rt.DefaultIfEmpty() orderby r.Year select new { Year = r.Year, Champion = r.Name, Constructor = t == null ? "no constructor championship" : t.Name }); Console.WriteLine("Year Champion\t\t Constructor Title"); foreach (var item in racersAndTeams) { Console.WriteLine("{0}: {1,-20} {2}", item.Year, item.Champion, item.Constructor); }
10.組連接
組連接類似內連接,內連接通過某一項連接兩個數據源(如 r.Year equals t.Year),組連接使用一組項連接,例如下麵的例子,
通過 new
{
FirstName = r.FirstName,
LastName = r.LastName
}
equals
new
{
FirstName = r2.FirstName,
LastName = r2.LastName
}
連接兩個數據源
var racers = Formula1.GetChampionships() .SelectMany(cs => new List<RacerInfo>() { new RacerInfo { Year = cs.Year, Position = 1, FirstName = cs.First.FirstName(), LastName = cs.First.LastName() }, new RacerInfo { Year = cs.Year, Position = 2, FirstName = cs.Second.FirstName(), LastName = cs.Second.LastName() }, new RacerInfo { Year = cs.Year, Position = 3, FirstName = cs.Third.FirstName(), LastName = cs.Third.LastName() } }); var q = (from r in Formula1.GetChampions() join r2 in racers on new { FirstName = r.FirstName, LastName = r.LastName } equals new { FirstName = r2.FirstName, LastName = r2.LastName } into yearResults select new { FirstName = r.FirstName, LastName = r.LastName, Wins = r.Wins, Starts = r.Starts, Results = yearResults }); foreach (var r in q) { Console.WriteLine("{0} {1}", r.FirstName, r.LastName); foreach (var results in r.Results) { Console.WriteLine("{0} {1}", results.Year, results.Position); } }
11.集合操作
擴展方法Distinct(),Union(),Intersect()(獲取交集),Except()都是集合操作。
//獲取同時駕駛Ferrari和駕駛McLaren獲得過冠軍的賽車手 static void SetOperations() { //定義一個委托,用來查詢駕駛Ferrari獲得過冠軍的賽車手和駕駛McLaren獲得過冠軍的賽車手 Func<string, IEnumerable<Racer>> racersByCar = car => from r in Formula1.GetChampions() from c in r.Cars where c == car orderby r.LastName select r; Console.WriteLine("World champion with Ferrari and McLaren"); //使用Intersect方法獲取兩個數據源的交集 foreach (var racer in racersByCar("Ferrari").Intersect(racersByCar("McLaren"))) { Console.WriteLine(racer); } }
12.合併
Zip()方法是.NET 4.0新增的,允許用一個為此函數把兩個相關的序列合併為一個。
對於合併,第一個集合中的第一項與第二個集合的第一項合併,第一個集合中的第二項與第二個集合的第二項合併,以此類推。如果兩個序列的項數不同,Zip()方法就會在達到較小集合的末尾時停止。
第一個集合中的元素有一個Name屬性,第二個集合中的元素有LastName和Starts屬性。
在racerNames集合上使用Zip()方法,需要把第二個集合racerNamesAndStarts作為第一個參數。第二個參數是一個委托,它通過參數first接受第一個集合的元素,通過參數second接受第二個集合的元素。其實現代碼返回一個字元串:
var racerNames = from r in Formula1.GetChampions() where r.Country == "Italy" orderby r.Wins descending select new { Name = r.FirstName + " " + r.LastName }; var racerNamesAndStarts = from r in Formula1.GetChampions() where r.Country == "Italy" orderby r.Wins descending select new { LastName = r.LastName, Starts = r.Starts }; var racers = racerNames.Zip(racerNamesAndStarts, (first, second) => first.Name + ", starts: " + second.Starts); foreach (var r in racers) { Console.WriteLine(r); }
13.分區
擴展方法Take()和Skip()等的分區操作可用於分頁。
例如,在第一頁只顯示5個賽車手,下一頁顯示接下來的5個賽車手...
Skip(page * pageSize)方法調到指定索引出,忽略前面的數據。Take(pageSize)方法顯示pageSize條數據
int pageSize = 5; int numberPages = (int)Math.Ceiling(Formula1.GetChampions().Count() / (double)pageSize); for (int page = 0; page < numberPages; page++) { Console.WriteLine("Page {0}", page); var racers = (from r in Formula1.GetChampions() orderby r.LastName, r.FirstName select r.FirstName + " " + r.LastName). Skip(page * pageSize).Take(pageSize); foreach (var name in racers) { Console.WriteLine(name); } Console.WriteLine(); }
TakeWhile()和SkipWhile()方法,傳遞一個委托,滿足這個條件的數據就提取或跳轉:
public static IEnumerable<TSource> SkipWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
14.聚合操作符
聚合操作符(如Count(),Sum(),Min(),Max(),Average(),Aggregate())不返回一個序列,而是返回一個值。
例如,使用Count方法應用於Racer的Years屬性,篩選獲得冠軍次數超過3次的賽車手。因為多次使用r.Years.Count(),所以使用let子句定義了一個變數。
var query = from r in Formula1.GetChampions() let numberYears = r.Years.Count() where numberYears >= 3 orderby numberYears descending, r.LastName select new { Name = r.FirstName + " " + r.LastName, TimesChampion = numberYears }; foreach (var r in query) { Console.WriteLine("{0} {1}", r.Name, r.TimesChampion); }
Aggregate()方法傳遞一個委托,將數據源中的每個元素作為委托的參數,並使用指定的函數累加。
15.轉換操作符
LINQ基礎(一)(http://www.cnblogs.com/afei-24/p/6841361.html)提到,查詢會推遲到迭代數據項時才執行,使用轉換操作符會立即執行查詢,把查詢結果放在數組,列表和字典中。
//轉換為數組 var names = new List<string> { "Nino", "Alberto", "Juan", "Mike", "Phil" }; var namesWithJ = (from n in names where n.StartsWith("J") orderby n select n).ToList();
轉換為Lookup<TKey,TElement>
//把Car賽車屬性作為鍵,每個鍵關聯多個車手Racer var racers = (from r in Formula1.GetChampions() from c in r.Cars select new { Car = c, Racer = r }).ToLookup(cr => cr.Car, cr => cr.Racer); foreach (var v in racers) { Console.Write(v.Key+"........"); foreach (var k in racers[v.Key]) { Console.WriteLine(k); } }
ToLookup(cr => cr.Car, cr => cr.Racer)方法的一個重載版本傳遞一個鍵和一個元素選擇器
如果需要在非類型化的集合上使用LINQ查詢,可以使用Cast()方法,定義強類型化的查詢:
var list = new System.Collections.ArrayList(Formula1.GetChampions() as System.Collections.ICollection); var query = from r in list.Cast<Racer>() where r.Country == "USA" orderby r.Wins descending select r; foreach (var racer in query) { Console.WriteLine("{0:A}", racer); }
Cast<Racer>()將 System.Collections.IEnumerable 的元素強制轉換為指定的類型。
16.生成操作符
生成操作符Range(),Empty(),Repeat()方法不是擴展方法,而是返回序列的正常靜態方法。在LING to Object中,這些方法可用於Enumerable類。
Range()方法用來填充一個範圍的數字。第一個參數作為起始值,第二個參數作為要填充的項數:
var values = Enumerable.Range(1,20);
結果為1至20的集合。
可以把該結果與其它擴展方法合併:
var values = Enumerable.Range(1,20).Select(n=> n*3);
Empty()方法返回一個不返回值的迭代器,它用於需要一個集合的參數,其中可以給參數傳遞空集合。
Repeat()方法返回指定個數的重覆值的集合迭代器。