C# Record構造函數的行為更改詳解
如何更改 C# Record 構造函數的行為
Record[1] 是 C# 9 中的一個新功能。Record是從Structs[2]借用的特殊類, 因為它們具有 基於值的相等性,您可以將它們視為兩類類型之間的混合體。默認情況下,它們或多或少是不可變的,並且具有語法糖,使聲明更容易和更簡潔。但是,語法糖可能會掩蓋更多標準任務,例如更改默認構造函數的行為。在某些情況下,您可能需要這樣做以進行驗證。本文將向您展示如何實現這一目標。
以這個簡單的示例類為例:
public class StringValidator { public string InputString { get; } public StringValidator(string inputString) { if (string.IsNullOrEmpty(inputString)) throw new ArgumentNullException(nameof(inputString)); InputString = inputString; } }
很明顯,如果消費者試圖在沒有有效字符串的情況下創建此類的實例,他們將收到異常。創建Record的標準語法如下所示:
public record StringValidator(string InputString);
它既友好又簡潔,但並不清楚您將如何驗證字符串。此定義告訴編譯器將有一個名為 的屬性InputString,並且構造函數會將值從參數傳遞給該屬性。我們需要刪除語法糖來驗證字符串。幸運的是,這很容易。我們不需要使用新語法來定義我們的Record。我們可以定義類似於類的Record,但將關鍵字類更改為Record。
public record StringValidator { public string InputString { get; } public StringValidator(string inputString) { if (string.IsNullOrEmpty(inputString)) throw new ArgumentNullException(nameof(inputString)); InputString = inputString; } }
不幸的是,這意味著我們不能使用 非破壞性突變[3]。該with關鍵字給我們創造瞭一些屬性來更改Record的新版本的功能。這意味著我們不會修改Record的原始實例,但我們會得到它的副本。這是 Fluent API 和函數式編程的常用方法。這使我們能夠保持不變性。
為瞭允許非破壞性突變,我們需要添加init屬性訪問器。這與構造函數的工作方式類似,但僅在對象初始化期間調用。這是實現init訪問器的更完整的解決方案。這允許您共享構造函數邏輯和初始化邏輯。
using System; namespace ConsoleApp25 { class Program { static void Main(string[] args) { //This throws an exception from the constructor //var stringValidator = new StringValidator(null); var stringValidator1 = new StringValidator("First"); var stringValidator2 = stringValidator1 with { InputString = "Second" }; Console.WriteLine(stringValidator2.InputString); //This throws an exception from the init accessor //var stringValidator3 = stringValidator1 with { InputString = null }; //Output: Second } } public record StringValidator { private string inputString; public string InputString { get => inputString; init { //This init accessor works like the set accessor ValidateInputString(value); inputString = value; } } public StringValidator(string inputString) { ValidateInputString(inputString); InputString = inputString; } public static void ValidateInputString(string inputString) { if (string.IsNullOrEmpty(inputString)) throw new ArgumentNullException(nameof(inputString)); } } }
Record構造函數應該有邏輯嗎?
這是一個有爭議的辯論,超出瞭本文的范圍。很多人會爭辯說你不應該把邏輯放在構造函數中。Record的設計鼓勵您不要在構造函數或 init 訪問器中放置邏輯。一般來說,Record應該及時代表數據的快照狀態。您不需要應用邏輯,因為假設您知道此時數據的狀態。但是,就像其他所有編程結構一樣,無法知道Record可能會產生哪些用例。這是庫 Urls 中的[4]一個示例[5] , [6]它將 URL 視為不可變Record:
using System.Net; namespace Urls { public record QueryParameter { private string? fieldValue; public string FieldName { get; init; } public string? Value { get => fieldValue; init { fieldValue = WebUtility.UrlDecode(value); } } public QueryParameter(string fieldName, string? value) { FieldName = fieldName; fieldValue = WebUtility.UrlDecode(value); } public override string ToString() => $"{FieldName}{(Value != null ? "=" : "")}{WebUtility.UrlEncode(Value)}"; } }
我們確保在存儲查詢值時對其進行解碼,然後在將其用作 Url 的一部分時對其進行編碼。
你可能會問:為什麼不把一切都Record下來?似乎會有與此相關的陷阱,但我們正在冒險進入新領域,我們尚未為 C# 上下文中的Record制定最佳實踐。
總結
開發人員需要幾年時間才能接受Record並制定使用它們的基本規則。您目前有一張白紙,您可以自由嘗試,直到“專傢”開始告訴您其他情況。我的建議是隻使用Record來表示固定數據和最小邏輯。盡可能使用語法糖。但是,在某些情況下,構造函數中的最小驗證可能是可行的。運用你的判斷力,與你的團隊討論,權衡利弊。
到此這篇關於C# Record構造函數行為更改的文章就介紹到這瞭,更多相關C# Record構造函數內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
References
[1] Record: https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records
[2] Structs: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct
[3] 非破壞性突變: https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records#non-destructive-mutation
[4] Urls 中的: https://github.com/MelbourneDeveloper/Urls
[5] 示例: https://github.com/MelbourneDeveloper/Urls/blob/5f55a9437cfac1223711d616bfdbeb72b230d263/src/Uris/QueryParameter.cs#L5
[6] , : https://github.com/MelbourneDeveloper/Urls