Chapter 4 - Item 39 : Avoid Throwing Exceptions in Functions and Actions

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

利用迭代搭配委派輸入參數,可同時得到原始集合封裝性與自定義執行方法的彈性。接下來要來談一個實務上會遇到的問題;若是在迭代的過程中,執行的委派方法拋出了例外,且方法包含了修改集合元素狀態的邏輯,該如何回復前一個正常的狀態呢?以下用一個範例演示。

範例情境:

var allEmployees = FindAllEmployees( );
allEmployees.ForEach( e => e.MonthlySalary *= 1.05M );

若是在某一個元素執行委派方法時拋出了例外,系統無從得知該如何回覆先前狀態。

要解決這個問題,可以從兩個地方下手;一個是利用提前檢查元素狀態,若不符合前置條件則不執行(而非執行後拋出例外)。另一個是利用集合副本的方式,分配另一塊記憶體執行委派方法,若期間沒拋出例外則將原始物件指向新的副本。

做法一,利用前置條件檢查。

allEmployees.FindAll(
    e => e.Classification == EmployeeType.Active ).
    ForEach( e => e.MonthlySalary *= 1.05M );

做法二,利用集合副本重新指派新的物件。

var updates = ( from e in allEmployees
                select new Employee
                {
                    EmployeeID = e.EmployeeID,
                    Classification = e.Classification,
                    YearOfService = e.YearOfService,
                    MonthlySalary = e.MonthlySalary * 1.05M
                } ).ToList( );

allEmployees = updates;

兩種做法都有其適用情境,第一種做法需確保例外拋出不會發生在 e.MonthlySalary *= 1.05M,但不需額外記憶體空間;而第二種做法包含了所有可能的例外狀況,但需額外分配記憶體。

Note:若委派方法不包含改變集合元素狀態,則不需考慮其是否可回復先前狀態。

例如:

var total = allEmployees.Aggregate( 0M,
    ( sum, emp ) => sum + emp.MonthlySalary );
結論:
1. 避免在迭代中,拋出輸入委派方法的例外。

2. 若無可避免有機會拋出例外,可利用前置條件檢查或是集合副本的方式實作。