Chapter 4 - Item 43 : Use Single() and First() to Enforce Semantic Expectations on Queries

Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得

LINQ 讓我們很方便的可以操作集合,然而,LINQ 也可以幫助我們寫出更能表達意圖的程式。

Single():當搜尋條件超過一筆或沒有符合條件的結果時擲出例外,早期的擲出例外可以避免後許程式碼有不正常的行為(例如:寫入不正確的資料)。Single() 語意本身也很清楚的表明只要得到唯一元素而非集合。
SingleOrDefault():當搜尋條件超過一筆時擲出例外,而沒有符合條件的結果時回傳元素預設值(參考型別為 null)。當可允許搜尋不到結果時使用。

範例:

var somePeople = new List<Person>
{
	new Person{FirstName="Bill", LastName="Gates"},
	new Person{FirstName="Bill", LastName="Wagner"},
	new Person{FirstName="Bill", LastName="Johnson"}
}

// 集合不只一筆資料符合條件,丟出例外。
var answer = (from p in somePeople
	      where p.FirstName == "Bill"
	      select p).Single();

// 集合沒有資料符合條件,丟出例外。
var answer2 = (from p in somePeople
	      where p.FirstName == "Larry"
	      select p).Single();

// 回傳 null
var answer3 = (from p in somePeople
	       where p.FirstName == "Larry"
               select p).SingleOrDefault();

First():取得第一個符合條件的元素,若沒有符合條件的結果則擲出例外;可以搭配 Skip() 或 orderby 取得對應元素。
FirstOrDefault():取得第一個符合條件的元素,若沒有符合條件的結果則回傳元素預設值。當可允許搜尋不到結果時使用。

範例:

var forwards = new List<Forward>
{
	new Forward{GoalsScored=20},
	new Forward{GoalsScored=10},
	new Forward{GoalsScored=5},
};

var answer4 = (from f in forwards
	       where f.GoalsScored > 0
	       orderby f.GoalsScored
	       select f).FirstOrDefault();

// 若沒有符合條件的資料,擲出例外。
var answer5 = (from f in forwards
	       where f.GoalsScored > 0
               orderby f.GoalsScored
	       select f).First();

// 這裡不用 Take,原因是想取的值是單一個元素,而非整個集合。
var answer6 = (from f in forwards
	       where f.GoalsScored > 0
	       orderby f.GoalsScored
               select f).Skip(2).First(); // 取得第三個值

使用 Skip() 時語意已經相較不清楚。有使用 Skip() 需求時,應考慮是否使用支援索引的 IList<T> 直接對索引存取;或修改搜尋邏輯,讓回傳結果唯一。

結論:
1. 善加利用 Single(), SingleOrDefault(), First(), FirstOrDefault() 回傳元素,讓語意更加貼近實際需求。