這個做法是與寫法相關,建議是用 Lazy Evaluation
的延遲特性來處理需要共用邏輯的場合而不是寫一個方法把共用邏輯放進去。
例如下面這段程式碼用來查詢員工的薪資,這兩個語句只有年資不同其它條件都相同。
void Main()
{
var allEmployees = FindAllEmployees();
var earlyFolks = from e in allEmployees
where e.Classification == EmployeeType.Salary
where e.YearsOfService > 20
where e.MonthlySalary < 4000
select e;
var newest = from e in allEmployees
where e.Classification == EmployeeType.Salary
where e.YearsOfService < 2
where e.MonthlySalary < 4000
select e;
Console.WriteLine(earlyFolks);
Console.WriteLine(newest);
}
public IEnumerable<Employee> FindAllEmployees()
{
var result = new List<Employee>
{
new Employee{
Name = "User1",
Classification = EmployeeType.Salary,
MonthlySalary = 1000,
YearsOfService = 1
},
new Employee{
Name = "User2",
Classification = EmployeeType.Salary,
MonthlySalary = 3000,
YearsOfService = 21
},
new Employee{
Name = "User3",
Classification = EmployeeType.Salary,
MonthlySalary = 1500,
YearsOfService = 1
}
};
foreach (var element in result)
{
yield return element;
}
}
public class Employee
{
public string Name { get; set; }
public EmployeeType Classification { get; set; }
public int YearsOfService { get; set; }
public decimal MonthlySalary { get; set; }
}
public enum EmployeeType
{
Salary
}
所以秉持著不重複的原則可能會寫出下面這種程式碼,也就是把共用的邏輯移動到 LowPaidSalaried 方法裡面,這樣未來如果需求改變了只需要改動 LowPaidSalaried 的邏輯就好,但是這種寫法實際上執行起來會比第一種寫法還要慢。
private static bool LowPaidSalaried(Employee e) =>
e.MonthlySalary < 4000 &&
e.Classification == EmployeeType.Salary;
var allEmployees = FindAllEmployees();
var earlyFolks = from e in allEmployees
where LowPaidSalaried(e) && e.YearsOfService > 20
select e;
var newest = from e in allEmployees
where LowPaidSalaried(e) && e.YearsOfService < 2
select e;
要改善這種情況可以利用 Lazy Evaluation
的延遲特性把共用的語句編寫成擴充方法,這樣既可以讓程式碼看起來整潔也不會對效能造成太大影響。
void Main()
{
var allEmployees = FindAllEmployees();
// Find the first employees:
var salaried = allEmployees.LowPaidSalariedFilter();
var earlyFolks = salaried.Where(e => e.YearsOfService > 20);
// find the newest people:
var newest = salaried.Where(e => e.YearsOfService < 2);
Console.WriteLine(earlyFolks);
Console.WriteLine(newest);
}
public static class MyExtension
{
public static IEnumerable<Employee> LowPaidSalariedFilter
(this IEnumerable<Employee> sequence) => from s in sequence
where s.Classification == EmployeeType.Salary &&
s.MonthlySalary < 4000
select s;
}
上面用到的都是 IEnumerable<T>
它背後會把 lambda 轉換成委派 LINQ to Objects 就是這種運作模式,它通常是用來操作記憶體內保存的物件。
另一種則是需要進行額外轉換的 IQueryable<T>
它背後會把 lambda 轉換成 expression tree
,LINQ to SQL 就是這種運作模式,
它通常是用來查詢資料庫會用到的,因為它會把我們寫的語句整理起來後翻譯成 SQL 語法丟給資料庫執行。
Summary
這個做法的關鍵是把一個大型的查詢語句拆分成多個可共用的小語句,這個流程很適合用來建立 filter,特別是在資料庫讀取的過程中我們可以建立 IQueryable 的共用版本讓過濾的方法也能進行共用,這樣就可以在發送給資料庫前把條件加入到語句中。