這個做法講解了 Partial Classes
與 Partial Methods
建議的使用場景與背後原理。
Partial Classes
提供了一個機制能讓程式設計師把一個 Class
分割成多份,例如下面這段程式碼把 Employee
類別分割成兩份,
並且裡面包含著不同的方法,最後經過編譯器處理後會把它們重新集合起來,這種機制能讓我們把把一個大的類別分割成多個小類別,讓我們能更容易的讀懂內容。
public partial class Employee
{
public void DoWork() {}
}
public partial class Employee
{
public void GoToLunch() {}
}
public class Program
{
public static void Main()
{
Employee emp = new Employee();
emp.DoWork();
emp.GoToLunch();
}
}
另一種使用場景是在程式碼產生器,由於產生出來的程式碼不建議修改,因為可能把你修改的內容覆蓋掉,所以就可以透過 Partial Classes
把產生出來的程式碼分割成一份,然後把需要客製化的內容分割成另外一份就可以避免覆蓋的問題。
可以想像成同時有兩個開發者在開發同一個功能,並且由於是分割的檔案所以以後也沒有合併衝突的問題,這個對版本管理有幫助。
要想達到與程式碼產生器一起工作則需要添加一些額外的設計,這時候就可以用到 Partial Methods
,首先程式碼產生器需要在建立出來的程式碼中
穿插 hooks,這樣在未來才可以讓工程師把客製化的程式碼掛上流程,不過使用 Partial Methods
有一些限制例如:
- 只能是 void 方法。
- 不能是 abstract 或 virtual 方法。
- 不能包含 out 引數。
有三種成員型別建議可以加上 Partial Methods
這樣在未來能讓開發者增加監控程式碼或者調整類別的行為。
- Mutator Methods
- Event Handlers
- Constructors
Mutator Methods
Mutator methods
泛指那些會修改狀態的任何方法,我們可以在修改過程中穿插一些程式碼,這樣就能做到事前驗證或者
狀態修改後進行記錄,這裡建議提供兩個擴展點,分別是變更前與變更後。
例如我可以產生 ReportValueChanging
方法用來在狀態改變之前呼叫,用於驗證或中斷變更,ReportValueChanged
方法用來在狀態改變之後呼叫,用於處理後續邏輯。
下面這段程式碼新增了兩個 Partial Methods
特別是不需要提供實做細節也能編譯成功,另外注意到 UpdateValue
方法
在狀態修改前呼叫 ReportValueChanging
還有在狀態修改後呼叫 ReportValueChanged
。
public partial class GeneratedStuff
{
private struct ReportChange
{
public readonly int OldValue;
public readonly int NewValue;
public ReportChange(int oldValue, int newValue)
{
OldValue = oldValue;
NewValue = newValue;
}
}
private class RequestChange
{
public ReportChange Values { get; set; }
public bool Cancel { get; set; }
}
partial void ReportValueChanging(RequestChange args);
partial void ReportValueChanged(ReportChange values);
private int storage = 0;
public void UpdateValue(int newValue)
{
// Precheck the change
RequestChange updateArgs = new RequestChange
{
Values = new ReportChange(storage, newValue)
};
ReportValueChanging(updateArgs);
if (!updateArgs.Cancel)
{
storage = newValue;
ReportValueChanged(new ReportChange(storage, newValue));
}
}
}
這種做法的好處是就算 Partial Methods
不提供實做細節也不會影響編譯流程,因為編譯器會自動把這些沒有實做的方法移除掉,
這樣當使用者想加入額外邏輯就實做 Partial Methods
,不想也沒有關係,因為也不會有影響。
如果沒有提供實做細節,編譯器會把自動移除方法,類似下方這樣。
public void UpdateValue(int newValue)
{
RequestChange updateArgs = new RequestChange
{
Values = new ReportChange(this.storage, newValue)
};
if (!updateArgs.Cancel)
{
this.storage = newValue;
}
}
如果想要也可以提供細節,像下方這樣可以做到額外的日誌記錄功能,或者事前驗證功能。
public partial class GeneratedStuff
{
partial void ReportValueChanging(RequestChange args)
{
if (args.Values.NewValue < 0)
{
Console.WriteLine($@"Invalid value: {args.Values.NewValue}, canceling");
args.Cancel = true;
}
else
Console.WriteLine($@"Changing {args.Values.OldValue} to {args.Values.NewValue}");
}
partial void ReportValueChanged(ReportChange values)
{
Console.WriteLine($@"Changed{values.OldValue} to {values.NewValue}");
}
}
Event Handlers
其實事件想要做到的事情也差不多,就是在事件發生前與事件發生後提供兩個 hooks 讓我們掛上自己的程式碼,例如下面這樣。
public partial class GeneratedStuff
{
public event EventHandler<EventArgs> SomeEvent;
partial void BeforeEventTriggered();
partial void AfterEventTriggered();
public void TriggerEvent()
{
// 事件觸發前
BeforeEventTriggered();
// 觸發事件
SomeEvent?.Invoke(this, EventArgs.Empty);
// 事件觸發後
AfterEventTriggered();
}
}
Constructors
最後是建構函式,能夠在初始化過程中新增部分方法 Initialize()
,之後開發者就能透過實作此方法插入自己的初始化邏輯。
public partial class GeneratedStuff
{
private int storage;
partial void Initialize(); // 提供給開發者的初始化擴展點
public GeneratedStuff() : this(0) { } // 預設建構函式
public GeneratedStuff(int initialValue) // 帶參數建構函式
{
storage = initialValue;
Initialize(); // 呼叫部分方法以執行用戶邏輯
}
}
Summary
Partial Classes
與 Partial Methods
能夠讓程式碼產生器和開發者的合作更加靈活,並且讓開發者不會去修改自動生成的程式碼
避免被覆蓋的風險,也能在事件或建構函式中穿插 Partial Methods
提供擴展點做到日誌追蹤等功能,關鍵就是提供擴展點避免人們手動去修改
更關鍵的程式碼,這種模式很適合用來開發框架。