七個“危險”高效的C#特性:讓冗余代碼徹底消失
為什么這篇博客很重要
你的工作不是寫setter、空值檢查或try-catch-finally,但大多數C#代碼庫卻逼著你做這些。
現代C#(9-13版本)通過以下特性消除了這些樣板代碼:
? 編譯器強制的契約
? 運行時優化的行為
? 微軟背書的模式(在Azure和Copilot中使用)
本文將介紹7個“危險”高效的特性,讓你刪掉那些本就不該存在的代碼。
深入探討前:先看看痛點(和解決方案)
1. required + init:告別構造函數
這對組合讓你在編譯時快速發現錯誤,無需構造函數、魔術字符串和空值,就能構建DTO和配置。
之前的寫法:
public class User {
public string FirstName { get; set; }
public string LastName { get; set; }
public User(string first, string last) {
FirstName = first;
LastName = last;
}
}之后的寫法(C# 11+):
public class User {
public required string FirstName { get; init; }
public required string LastName { get; init; }
// 編譯時檢查必填項,屬性不可變
}無需手動驗證或構造函數。缺少必填屬性會在編譯時直接報錯——這是最佳的錯誤發現時機。
參考:required修飾符 — Microsoft Docs
2. 模式匹配:比if更智能
模式匹配已全面升級:
? 類型模式
? 屬性模式
? 遞歸和列表模式(C# 12+)
實際API示例:
return request.Method switch {
"GET" => HandleGet(request),
"POST" => HandlePost(request),
_ => Results.BadRequest()
};屬性模式:
if (person is Employee { Salary: > 100_000 }) {
GiveBonus(person);
}列表模式:
if (nums is [_, _, 42, ..])
Console.WriteLine("在第三個位置找到42。");借助深層模式,你甚至可以匹配嵌套的對象結構和集合——無需空值鏈和類型檢查。
參考:模式匹配 — Microsoft Docs
3. record + with:不可變的清晰表達
想要100%類型安全、不可變的數據模型,又不想用AutoMapper或反射?
試試record和with。
之前的寫法:
var updated = new Order {
Id = original.Id,
Customer = original.Customer,
Status = "Shipped"
};之后的寫法:
var updated = original with { Status = "Shipped" };? 無需映射工具
? 無易變性bug
? 語言內置的復制語義
參考:record類型 — Microsoft Docs
4. InterpolatedStringHandler:零分配日志
.NET 6為日志添加了編譯器魔法:
如果日志級別被禁用,插值字符串甚至不會被計算——無分配,無性能損耗。
不推薦:
_logger.LogDebug($"Order {order.Id} processed for {order.Customer}");推薦:
_logger.LogDebug("Order {OrderId} processed for {Customer}", order.Id, order.Customer);- 快速
- 結構化
- 零插值成本
Microsoft.Extensions.Logging在幕后使用InterpolatedStringHandler優化日志格式化——而LoggerMessage.Define()通過預編譯委托讓這一過程更快。
參考:InterpolatedStringHandler — Microsoft Docs
5. CallerArgumentExpression:告別nameof()
拋出異常時,想讓參數名自動填充?
定義一次:
public static void ThrowIfNull<T>(
T argument,
[CallerArgumentExpression("argument")] string? name = null)
=> _ = argument ?? throw new ArgumentNullException(name);隨處使用:
ThrowIfNull(user); // 異常信息:“值不能為 null。(參數 'user')”一個可重用的輔助方法,替代數十個繁瑣的nameof()調用。
參考:CallerArgumentExpression — Microsoft Docs
6. await using:無痛異步清理
如果你的類型實現了IAsyncDisposable,這應該成為你的新默認寫法。
之前的寫法:
var conn = await factory.CreateAsync();
try {
await conn.SendAsync(...);
}
finally {
await conn.DisposeAsync();
}之后的寫法:
await using var conn = await factory.CreateAsync();
await conn.SendAsync(...);尤其在Blazor、EF Core和ASP.NET中非常有用,這些場景中異步流或DbContext很常見。
在Entity Framework Core的DbContext中使用時,可防止異步泄漏并提高負載下的性能。
參考:IAsyncDisposable — Microsoft Docs
7. 源生成器:替代反射,提升性能
既然可以在構建時生成代碼,何必在運行時反射?
微軟在以下組件中使用了源生成器:
? System.Text.Json
? Microsoft.Extensions.Logging
? EF Core元數據
示例:JSON源生成
[JsonSerializable(typeof(Order))]
internal partial class OrderJsonContext : JsonSerializerContext { }這避免了ASP.NET中的反射,并在AOT場景中減小了輸出大小。
需要在csproj中設置JsonSourceGenerationMode或通過JsonSerializerContext設置。
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.0" />
</ItemGroup>參考:System.Text.Json源生成 — Microsoft Docs
bonus:file修飾符 = 真正的文件作用域類型
想要一個僅對當前文件可見的輔助類?
這樣寫:
file class Helper {
// 無法從項目的其他地方訪問
}非常適合內部靜態輔助工具、小型DSL或測試腳手架。
參考:file作用域類型 — C# 12 Docs
摘要表
(原內容未提供具體表格內容,此處保持原樣)
最終挑戰:從你的應用中刪除100行代碼
下一個PR建議:
? 用record + required + with重構一個DTO
? 用CallerArgumentExpression替代一個nameof()輔助方法
? 把一個try-finally換成await using
?? 然后運行差異對比。看著樣板代碼消失——且不會破壞任何測試。
輪到你了
這些特性中,你已經在使用哪些?
你會在下一個項目中重構哪一個?



























