逐步解說:在 C# 中撰寫查詢 (LINQ)
本逐步解說示範用來撰寫 LINQ 查詢運算式的 C# 語言功能。當您完成本逐步解說之後,便可繼續參閱您想了解的特定 LINQ 提供者 (Provider) 的範例和文件,例如 LINQ to SQL、LINQ to DataSet 或 LINQ to XML。
必要條件
這個逐步解說需要在 Visual Studio 2008 中加入的功能。
如需本主題的影片版本,請參閱影片 HOW TO:在 C# 中撰寫查詢 (LINQ) (英文)。
建立 C# 專案
若要建立專案
啟動 Visual Studio。
在功能表列上,選擇 [檔案]、[新增]、[專案]。
[新增專案] 對話方塊隨即開啟。
展開 [安裝],展開 [範本],展開 [Visual C#],然後選取 [主控台應用程式]。
在 [名稱] 文字方塊中,輸入不同的名稱或接受預設名稱,然後選擇 [OK] 按鈕。
新專案即會出現於 [方案總管] 中。
請注意,您的專案具有 System.Core.dll 參考,以及 System.Linq 命名空間的 using 指示詞。
建立記憶體中資料來源
查詢的資料來源是一份簡單的 Student 物件清單。每一筆 Student 記錄都有名字、姓氏,以及代表學生考試分數的整數陣列。請將這段程式碼複製到您的專案,並注意下列特性:
Student 類別是由自動實作的屬性組成。
清單中的每個學生都是使用物件初始設定式來初始化。
清單本身是使用集合初始設定式來初始化。
這套完整的資料結構將在不會明確呼叫任何建構函式或明確成員存取的情況下進行初始化和具現化。如需這些新功能的詳細資訊,請參閱自動實作的屬性 (C# 程式設計手冊) 和物件和集合初始設定式 (C# 程式設計手冊)。
若要加入資料來源
將 Student 類別和已初始化的學生名單加入至專案中的 Program 類別。
public class Student { public string First { get; set; } public string Last { get; set; } public int ID { get; set; } public List<int> Scores; } // Create a data source by using a collection initializer. static List<Student> students = new List<Student> { new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 92, 81, 60}}, new Student {First="Claire", Last="O’Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}}, new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {88, 94, 65, 91}}, new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {97, 89, 85, 82}}, new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {35, 72, 91, 70}}, new Student {First="Fadi", Last="Fakhouri", ID=116, Scores= new List<int> {99, 86, 90, 94}}, new Student {First="Hanying", Last="Feng", ID=117, Scores= new List<int> {93, 92, 80, 87}}, new Student {First="Hugo", Last="Garcia", ID=118, Scores= new List<int> {92, 90, 83, 78}}, new Student {First="Lance", Last="Tucker", ID=119, Scores= new List<int> {68, 79, 88, 92}}, new Student {First="Terry", Last="Adams", ID=120, Scores= new List<int> {99, 82, 81, 79}}, new Student {First="Eugene", Last="Zabokritski", ID=121, Scores= new List<int> {96, 85, 91, 60}}, new Student {First="Michael", Last="Tucker", ID=122, Scores= new List<int> {94, 92, 91, 91} } };
若要將新 Student 加入至 Students 清單
- 將新的 Student 加入至 Students 清單,並使用您選擇的名稱和考試分數。請嘗試輸入所有的新學生資訊,以深入了解物件初始設定式的語法。
建立查詢
若要建立簡單的查詢
在應用程式的 Main 方法中建立簡單的查詢,此查詢會在執行後產生一份學生名單,列出第一次考試分數高於 90 分的學生。請注意,因為選取的是整個 Student 物件,所以查詢的型別是 IEnumerable<Student>。雖然程式碼也可以透過 var 關鍵字使用隱含型別,但是這裡會使用明確型別清楚說明結果 (如需 var 的詳細資訊,請參閱隱含型別區域變數 (C# 程式設計手冊))。
請注意,查詢的範圍變數 student 是做為來源中各個 Student 的參考,以提供每個物件的成員存取權。
// Create the query.
// The first line could also be written as "var studentQuery ="
IEnumerable<Student> studentQuery =
from student in students
where student.Scores[0] > 90
select student;
執行查詢
若要執行查詢
現在,請撰寫可以讓查詢開始執行的 foreach 迴圈。請注意與程式碼有關的下列事項:
傳回的序列中,每個項目都是透過 foreach 迴圈中的反覆運算變數來存取。
這個變數的型別是 Student,而查詢變數的型別則是相容的 IEnumerable<Student>。
在您加入這段程式碼之後,請按 Ctrl + F5 以建置並執行應用程式,並在 [主控台] 視窗中查看結果。
// Execute the query.
// var could be used here also.
foreach (Student student in studentQuery)
{
Console.WriteLine("{0}, {1}", student.Last, student.First);
}
若要加入其他篩選條件
您可以在 where 子句中合併多個布林值條件,進一步限定查詢範圍。下列程式碼會加入條件,讓查詢傳回第一次分數高於 90 分和最後一次成績低於 80 分的學生。where 子句應該與下列程式碼類似。
where student.Scores[0] > 90 && student.Scores[3] < 80
如需詳細資訊,請參閱where 子句 (C# 參考)。
修改查詢
若要排序結果
當結果按照某種順序排列時,會比較好瀏覽。您可以依照來源項目中可供存取的任何欄位來排序傳回的序列。例如,下列 orderby 子句是依據每個學生的姓氏,按照字母順序由 A 到 Z 排序結果。請緊接著 where 陳述式後面以及 select 陳述式前面,將下列 orderby 子句加入到您的查詢中:
orderby student.Last ascending
現在,請變更 orderby 子句,讓它根據第一次考試的分數,按照由分數最高到最低的相反順序來排序結果。
orderby student.Scores[0] descending
變更 WriteLine 格式字串,以便您檢視分數。
Console.WriteLine("{0}, {1} {2}", student.Last, student.First, student.Scores[0]);
如需詳細資訊,請參閱orderby 子句 (C# 參考)。
若要將結果分組
群組是查詢運算式中非常強大的一項功能。內含 group 子句的查詢會產生群組序列,而且每個群組本身都包含 Key,以及由該群組所有成員組成的序列。下面的新查詢使用學生姓氏的第一個字母當做索引鍵,將學生分組。
// studentQuery2 is an IEnumerable<IGrouping<char, Student>> var studentQuery2 = from student in students group student by student.Last[0];
請注意,查詢的型別現在已經變更。它現在會產生使用 char 型別做為索引鍵的群組序列,以及 Student 物件的序列。由於查詢的型別已經變更,因此下列程式碼也會變更 foreach 執行迴圈:
// studentGroup is a IGrouping<char, Student> foreach (var studentGroup in studentQuery2) { Console.WriteLine(studentGroup.Key); foreach (Student student in studentGroup) { Console.WriteLine(" {0}, {1}", student.Last, student.First); } }
按 Ctrl + F5 執行應用程式,並於 [主控台] 視窗檢視結果。
如需詳細資訊,請參閱group 子句 (C# 參考)。
若要讓變數變成隱含型別
明確撰寫 IGroupings 的 IEnumerables 程式碼可能很快就會變得非常瑣碎無聊。這時候,您可以改用 var,以更便利的方式撰寫相同的查詢和 foreach 迴圈。var 關鍵字不會變更物件的型別,只會指示編譯器推斷型別。變更 studentQuery 的型別和反覆運算變數 group到 var 並重新執行查詢。請注意,在內部 foreach 迴圈中,反覆運算變數的型別仍然是 Student,而且查詢的運作方式也和以前完全一樣。請將 s 反覆運算變數變更為 var,然後重新執行查詢。您將取得完全相同的結果。
var studentQuery3 = from student in students group student by student.Last[0]; foreach (var groupOfStudents in studentQuery3) { Console.WriteLine(groupOfStudents.Key); foreach (var student in groupOfStudents) { Console.WriteLine(" {0}, {1}", student.Last, student.First); } }
如需 var 的詳細資訊,請參閱 隱含型別區域變數 (C# 程式設計手冊)。
若要依索引鍵值排序群組
執行前述查詢時,您應該會注意到群組並未依字母順序排列。如果要變更這個狀況,您必須在 group 子句後面提供 orderby 子句。但是您必須先取得識別項做為 group 子句所建立之群組的參考,才能使用 orderby 子句。請使用 into 關鍵字提供這個識別項,如下所示:
var studentQuery4 = from student in students group student by student.Last[0] into studentGroup orderby studentGroup.Key select studentGroup; foreach (var groupOfStudents in studentQuery4) { Console.WriteLine(groupOfStudents.Key); foreach (var student in groupOfStudents) { Console.WriteLine(" {0}, {1}", student.Last, student.First); } }
執行這項查詢時,您會發現群組現在已經按照字母順序排序。
若要使用 let 引入識別項
您可以使用 let 關鍵字,在查詢運算式中引入任何運算式結果的識別項。如下列範例所示,這個識別項十分方便,它也可以儲存運算式的結果,如此一來就不需要進行多次計算,藉以提高效能。
// studentQuery5 is an IEnumerable<string> // This query returns those students whose // first test score was higher than their // average score. var studentQuery5 = from student in students let totalScore = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3] where totalScore / 4 < student.Scores[0] select student.Last + " " + student.First; foreach (string s in studentQuery5) { Console.WriteLine(s); }
如需詳細資訊,請參閱let 子句 (C# 參考)。
若要在查詢運算式中使用方法語法
如LINQ 中的查詢語法及方法語法 (C#) 所述,某些查詢作業只能使用方法語法來表示。下列程式碼範例會計算來源序列中每個 Student 的總分數,然後對該項查詢的結果呼叫 Average() 方法,以計算該班級的平均分數。請注意查詢運算式前後的括號位置。
var studentQuery6 = from student in students let totalScore = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3] select totalScore; double averageScore = studentQuery6.Average(); Console.WriteLine("Class average score = {0}", averageScore);
若要在 select 子句中進行轉換或投影
在查詢所產生的序列中,其項目經常與來源序列中的項目不同。請刪除先前的查詢和執行迴圈標記或為它們加上註解,然後使用下列程式碼取代。請注意,查詢會傳回字串序列 (不是 Students),而且 foreach 迴圈中也會反映這個情況。
IEnumerable<string> studentQuery7 = from student in students where student.Last == "Garcia" select student.First; Console.WriteLine("The Garcias in the class are:"); foreach (string s in studentQuery7) { Console.WriteLine(s); }
本逐步解說前面的程式碼指出班級平均成績約為 334 分。若要產生總分數高於班級平均的 Students 序列以及其 Student ID,您可以在 select 陳述式中使用匿名型別:
var studentQuery8 = from student in students let x = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3] where x > averageScore select new { id = student.ID, score = x }; foreach (var item in studentQuery8) { Console.WriteLine("Student ID: {0}, Score: {1}", item.id, item.score); }
後續步驟
在您熟悉於 C# 中使用查詢的基本概念之後,便可開始閱讀您想了解的特定 LINQ 提供者類型的文件和範例。