Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得
程式的“狀態”是開發者要關心的,任何操作都有可能拋出例外;如何確保在擲出例外時程式的可靠度(資料的完整性)是一門課題。以下作者提出幾種針對確保資料完整性的做法。
主要分為三個步驟:
1. 建立物件的副本。
2. 修改副本的狀態(包含可能拋出例外的操作)。
3. 若操作成功,將副本與原始值交換;完成更新操作。
範例:
PayrollData _payrollData = new PayrollData();
public void PhysicalMove(string title, decimal newPay)
{
// PayrollData 是值型別。
// 如果參數無效,建構子會拋出例外。
var d = new PayrollData(title, newPay, _payrollData.DateOfHire);
// 如果 d 正確建構,則將目前值與 d 交換。
_payrollData = d;
}
public struct PayrollData
{
public string Title { get; set; }
public decimal Pay { get; set; }
public DateTime DateOfHire { get; set; }
public PayrollData(string title, decimal pay, DateTime dateOfHire)
{
Title = title;
Pay = pay;
DateOfHire = dateOfHire;
}
}
若欲修改的目標是參考型別,情況就不太一樣。
private IList<PayrollData> _data;
public IList<PayrollData> MyCollection => _data;
public void UpdateData()
{
// 可能擲出例外的操作。
var temp = UnreliableOperation();
// 如果正確執行,將 temp 參考指定給 _data。
_data = temp;
}
private List<PayrollData> UnreliableOperation()
{
return new List<PayrollData>();
}
外部可能已透過 MyCollection 屬性取得 _data 參考,當呼叫 UpdateData 後;外部仍參考著原始的 _data 參考(除非再次透過 MyCollection 取得參考)。這樣會造成資料不同步的問題。
修改後版本:
private IList<PayrollData> _data;
public IList<PayrollData> MyCollection => _data;
public void UpdateData()
{
// 可能擲出例外的操作。
var temp = UnreliableOperation();
// 如果正確執行,清空 _data 並將 temp 元素加入 _data。
_data.Clear();
foreach (var item in temp)
_data.Add(item);
}
private List<PayrollData> UnreliableOperation()
{
return new List<PayrollData>();
}
也可以將資料物件封裝起來:
public class Envelop : IList<PayrollData>
{
private List<PayrollData> _data = new List<PayrollData>();
public void SafeUpdate(IEnumerable<PayrollData> souceList)
{
// 可能拋出例外的操作
var updates = new List<PayrollData>(souceList.ToList());
// 若沒有例外發生,更新 _data。
_data = updates;
}
public PayrollData this[int index]
{
get => _data[index];
set => _data[index] = value;
}
public int Count => _data.Count;
public bool IsReadOnly => ((IList<PayrollData>)_data).IsReadOnly;
public void Add(PayrollData item) => _data.Add(item);
public void Clear() => _data.Clear();
public bool Contains(PayrollData item) =>_data.Contains(item);
public void CopyTo(PayrollData[] array, int arrayIndex)
=> _data.CopyTo(array,arrayIndex);
public IEnumerator<PayrollData> GetEnumerator()
=> _data.GetEnumerator();
public int IndexOf(PayrollData item)
=> _data.IndexOf(item);
public void Insert(int index, PayrollData item)
=> _data.Insert(index,item);
public bool Remove(PayrollData item)
=> _data.Remove(item);
public void RemoveAt(int index)
=> _data.RemoveAt(index);
IEnumerator IEnumerable.GetEnumerator()
=> _data.GetEnumerator();
}
讓 Envelop 實作 IList,讓外部直接存取 _data 內元素而非是擁有 _data 參考副本;這樣就解決的資料不同步的問題。
結論:
1. 在拋出例外時,同時需注意資料物件的狀態正確性。
2. 並非所有操作都需要拋出例外。
a. Dispose, Finalizer 皆不可拋出例外,避免資源洩漏。
b. Exception filter 不可拋出例外,這會造成外部無法取得原始的例外訊息。
c. 委派鏈中方法不應拋出例外,其中某一方法擲出例外會造成後續方法不會執行。
1. 在拋出例外時,同時需注意資料物件的狀態正確性。
2. 並非所有操作都需要拋出例外。
a. Dispose, Finalizer 皆不可拋出例外,避免資源洩漏。
b. Exception filter 不可拋出例外,這會造成外部無法取得原始的例外訊息。
c. 委派鏈中方法不應拋出例外,其中某一方法擲出例外會造成後續方法不會執行。