Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得
本節將介紹從簡單至複雜的 Linq query 方法,所有的 query 語法將被編譯器轉譯成 method call;接著才會選擇最適合的呼叫方法(根據型別)。
1. Where, Select.
// query
int[ ] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var smallNumbers = from num in numbers
where num < 5
select num;
// method call, select operartion was optimized away.
var smallNumbers = numbers.Where( n => n < 5 );
由於 where 語句將立刻回傳元素,轉譯成 method call 後 select 將被優化省略。
// query
var allNumbers = from n in numbers
select n;
// method call, select opeartion can't be optimized.
var allNumbers = numbers.Select( n => n );
此時 select 將無法被優化。
// query
var smallNumbers = from n in numbers
where n < 5
select n * n;
// method call
var smallNumbers = numbers.Where( n => n < 5 ).
Select( n => n * n );
select 回傳值透過自定義運算,同樣也無法優化省略。
而 select 較常應用於回傳匿名物件集合,回傳值型別可以彈性運用。
// query
var squares = from n in numbers
select new
{
Number = n,
Square = n * n
};
// method call
var squares = numbers.Select( n =>
new
{
Number = n,
Square = n * n
} );
2. OrderBy, ThenBy, OrderByDescending, ThenByDescending.
var employees = new List<Employee>( );
// query
var people = from e in employees
where e.Age > 30
orderby e.LastName, e.FirstName, e.Age
select e;
// method call
people = employees.Where( e => e.Age > 30 ).
OrderBy( e => e.LastName ).
ThenBy( e => e.FirstName ).
ThenBy( e => e.Age );
取得的集合將依照所有 Employee Age 大於 30 的情況排序。先比對 LastName,若相同則接著比對 FirstName,再相同則比對 Age。
需注意以下的 query 會有完全不同的行為。
// Not correct. Sorts the entire sequence three times.
people = from e in employees
where e.Age > 30
orderby e.LastName
orderby e.FirstName
orderby e.Age
select e;
此 query 將會依照 LastName, FirstName, Age 依序完整排序,也就是集合執行了三次完整的排序!
有時我們也需要降冪排序,在 query 寫法中也很容易做到。
// seperate queries
people = from e in employees
where e.Age > 30
orderby e.LastName descending, e.FirstName, e.Age
select e;
此時 LastName 將降冪排序,若發現相同會繼續比較 FirstName, Age。
3. GroupBy.
GroupBy 用來將物件依特定屬性作為鍵值分類集合。
var employees = new List<Employee>( );
var results = from e in employees
group e by e.Department into d
select new
{
Department = d.Key,
Size = d.Count( )
};
// First, query was translated into a nested query.
results = from d in from e in employees group e by e.Department
select new
{
Department = d.Key,
Size = d.Count( )
};
// method call
results = employees.GroupBy( e => e.Department ).
Select( e => new
{
Department = e.Key,
Size = e.Count( )
} );
此 query 將 Employee Department 作為鍵值,select 回傳匿名物件有兩個屬性,分別為:鍵值(Department)、符合對應鍵值的 Employee 數量(Size)。
query 也可以回傳目標鍵值的集合枚舉:
// Get Employees in each group
var groupResults = from e in employees
group e by e.Department into d
select new
{
Department = d.Key,
Employees = d.AsEnumerable( )
};
// method call
groupResults = employees.GroupBy( e => e.Department ).
Select( e => new
{
Department = e.Key,
Employees = e.AsEnumerable( )
} );
4. SelectMany, Join, GroupJoin.
SelectMany :
int[ ] odds = { 1, 3, 5, 7 };
int[ ] evens = { 2, 4, 6, 8 };
// query
var values = from oddNumber in odds
from evenNumber in evens
select new
{
oddNumber,
evenNumber,
Sum = oddNumber + evenNumber
};
// flatten query by SelectMany
var values = odds.SelectMany( oddNumber => evens,
( oddNumber, evenNumber ) =>
new
{
oddNumber,
evenNumber,
Sum = oddNumber + evenNumber
} );
// query
var values = from oddNumber in odds
from evenNumber in evens
where oddNumber > evenNumber
select new
{
oddNumber,
evenNumber,
Sum = oddNumber + evenNumber
};
// full query is translated into this statement.
var values = odds.SelectMany( oddNumber => evens,
( oddNumber, evenNumber ) =>
new
{
oddNumber,
evenNumber
} ).
Where( pair => pair.oddNumber > pair.evenNumber ).
Select( pair =>
new
{
pair.oddNumber,
pair.evenNumber,
Sum = pair.oddNumber + pair.evenNumber
} );
當遇到三個以上的集合時,同樣利用 SelectMany 方法組合。
// query
var triples = from n in new int[ ] { 1, 2, 3 }
from s in new string[ ] { "one", "two", "three" }
from r in new string[ ] { "I", "II", "III" }
select new
{
Arabic = n,
Word = s,
Roman = r
};
// method call
var numbers = new int[ ] { 1, 2, 3 };
var words = new string[ ] { "one", "two", "three" };
var raomanNumerals = new string[ ] { "I", "II", "III" };
var triples = numbers.SelectMany( n => words,
( n, s ) =>
new
{
n,
s
} ).
SelectMany( pair => raomanNumerals,
( pair, r ) =>
new
{
Arabic = pair.n,
Word = pair.s,
Roman = r
} );
Join :
var numbers = new int[ ] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var labels = new string[ ] { "0", "0", "2", "3", "4", "5", "6", "7", "8", "9" };
// query
var query = from num in numbers
join label in labels on num.ToString( ) equals label
select new
{
num,
label
};
// method call
var query = numbers.Join( labels, num =>
num.ToString( ),
label => label,
( num, label ) =>
new
{
num,
label
} );
此 query 將輸出在 labels 所有元素中,符合 num.ToString( ) == label 的匿名物件,在此例 { num = 0, label = "0" } 將會有兩個元素,num = 1 的情況由於找不到符合元素將不存在輸出集合中。
GroupJoin :
public class Project
{
public int Id { get; set; }
}
public class Task
{
public Project Parent { get; set; }
}
var projects = new List<Project>( );
var tasks = new Tasks( );
// query
var groups = from p in projects
join t in tasks on p equals t.Parent
into projectTasks
select new
{
Project = p,
projectTasks
};
// method call
var groups = projects.GroupJoin( tasks,
p => p, t => t.Parent, ( p, projectTasks ) =>
new
{
Project = p,
TaskList = projectTasks
} );
此 query 將 tasks 元素裡的 Project 屬性與 Projects 中元素做比對,回傳的匿名物件屬性包括:Project, 符合 Task.Project == Project 的 Task 集合。
1. 了解 query 如何轉譯成 method call。
2. 底層提供的 query 與 method call 已可滿足大部分需求;若需自定義 query,需留意 query 與對應的 method call 是否行為一致。