.NET 執行平台(CLR)提供自動記憶體管理、JIT 編譯、型別安全等服務,但錯誤的程式設計模式會抵消這些優勢。本文針對終結化、托管/非托管轉換、實值型別 vs 參考型別、字串操作、方法內聯、現代高效能 API(Span/Memory/ArrayPool/ValueTask)提供可落地的優化策略與基準測試建議。
IDisposable:避免 GC 雙重收集代價~Finalizer() 的物件 → 配置時登記於「終結化佇列」。Finalize → **下一次 GC 才回收記憶體**。IDisposable 模式抑制終結化
public sealed class ResourceHolder : IDisposable
{
private IntPtr _handle;
private bool _disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // 關鍵:移出終結化佇列
}
private void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing) { /* 釋放受控資源 */ }
if (_handle != IntPtr.Zero) { Native.Release(_handle); _handle = IntPtr.Zero; }
_disposed = true;
}
~ResourceHolder() => Dispose(false); // 僅作為安全網
}
最佳實務:能實作 IDisposable 就不寫 Finalizer;非受控資源封裝於 SafeHandle 衍生類別,完全移除 Finalizer。
SafeHandle:框架級非受控資源封裝
internal sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeLibraryHandle() : base(true) { }
public static SafeLibraryHandle Load(string path) =>
new SafeLibraryHandle { handle = Native.LoadLibrary(path) };
protected override bool ReleaseHandle() => Native.FreeLibrary(handle);
}
Dispose/Finalize,消除錯誤風險。SecurityPermissionFlags.UnmanagedCode)。
// 不佳:迴圈中逐筆呼叫
foreach (var id in ids) Native.Process(id);
// 優化:批次 API 或緩衝區傳遞
Native.ProcessBatch(ids, ids.Length); // 單次轉換處理陣列
// 或
var buffer = Marshal.AllocHGlobal(ids.Length * sizeof(int));
try { Marshal.Copy(ids, 0, buffer, ids.Length); Native.ProcessBuffer(buffer, ids.Length); }
finally { Marshal.FreeHGlobal(buffer); }
Span<T>/Memory<T> 零複製互操作
// .NET 5+ 支援 Span 釘選
Span span = stackalloc byte[256];
unsafe {
fixed (byte* ptr = span) {
Native.FillBuffer(ptr, span.Length); // 無複製、無釘選開銷(堆疊記憶體)
}
}
// 或使用 MemoryMarshal.GetArrayDataReference 配合固定大小陣列
structreadonly struct(C# 7.2+)防禦性複製最佳化。ref struct(C# 7.2+)僅限堆疊(Span<T>、ref struct 自訂)。| 程式碼 | 裝箱原因 | 修正 |
|---|---|---|
object o = myStruct; | 指派給 object/介面 | 使用泛型 T、IEquatable<T>、ISpanFormattable |
Console.WriteLine(myStruct); | WriteLine(object) 多載 | Console.WriteLine(myStruct.ToString()) 或實作 ISpanFormattable |
list.Add(myStruct); (ArrayList) | 非泛型集合 | 改用 List<T> |
var eq = myStruct.Equals(other); | 呼叫 ValueType.Equals(object) | 實作 IEquatable<T>、覆寫 Equals(T) |
StringBuilder、string.Create、Utf8String、插值處理器
// 不佳:每次連接配置新字串
string s = "";
for (int i = 0; i < 1000; i++) s += i.ToString(); // 1000 次配置、複製
// 良好:StringBuilder
var sb = new StringBuilder(1000 * 4);
for (int i = 0; i < 1000; i++) sb.Append(i);
string result = sb.ToString();
// 最佳(.NET Core 2.1+):string.Create + 插值處理器
string result = string.Create(1000 * 4, 1000, static (span, count) => {
var handler = new System.Text.StringBuilderInterpolatedStringHandler(0, 0, null, span);
for (int i = 0; i < count; i++) handler.AppendFormatted(i);
});
// 避免 UTF-16 → UTF-8 轉換
var utf8Bytes = "Hello World"u8.ToArray(); // C# 11+ UTF-8 字串常值
// 或
var writer = new System.IO.Pipelines.PipeWriter(stream);
writer.Write("Hello World"u8);
writer.FlushAsync();
AggressiveInlining、sealed 消除虛擬呼叫MethodImplOptions.AggressiveInlining 放寬)。try/catch、lock、虛擬呼叫、await 等)。
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Add(int a, int b) => a + b;
// 熱路徑小型方法建議標記
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsValid(this T? obj) where T : class => obj != null;
sealed 類別/方法消除虛擬調度
public sealed class LeafService : IService { // 類別 sealed → 所有虛擬方法隱含 sealed
public void Execute() { } // 非虛擬,直接呼叫
}
public class Base {
public virtual void Compute() { }
}
public sealed class Derived : Base {
public sealed override void Compute() { } // 封鎖進一步覆寫,JIT 可內聯
}
| API | 用途 | 關鍵優勢 |
|---|---|---|
Span<T> / Memory<T> | 記憶體切片、零複製子範圍 | 避免陣列複製、支援堆疊/非受控/托管記憶體統一存取 |
ArrayPool<T>.Shared | 陣列重用池 | 減少 Gen 0/1 GC 壓力、避免大型陣列進入 LOH |
ValueTask<T> | 同步完成路徑無配置 | 避免 Task<T> 物件配置、支援 IValueTaskSource |
MemoryMarshal | 記憶體進階操作 | CreateSpan、AsBytes、Cast 零複製型別重新解讀 |
System.Buffers.Binary.BinaryPrimitives | 端序無關二進位讀寫 | 無位移/遮罩手寫、JIT 內聯單指令 |
string.Create / 插值處理器 | 直接寫入字串緩衝區 | 單次配置、無中間 StringBuilder |
Utf8String (C# 11+) | UTF-8 原生字串 | 避免編碼轉換、互操作/網路/日誌直通 |
// BenchmarkDotNet 範例
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net80)]
public class StringBenchmarks
{
[Benchmark(Baseline = true)]
public string ConcatLoop() {
string s = "";
for (int i = 0; i < 100; i++) s += i;
return s;
}
[Benchmark]
public string StringBuilder() {
var sb = new StringBuilder(400);
for (int i = 0; i < 100; i++) sb.Append(i);
return sb.ToString();
}
[Benchmark]
public string StringCreate() => string.Create(400, 100, static (span, count) => {
var h = new System.Text.StringBuilderInterpolatedStringHandler(0, 0, null, span);
for (int i = 0; i < count; i++) h.AppendFormatted(i);
});
}
典型結果:StringCreate 比 ConcatLoop 快 50–100 倍、配置量降低 99%。
效能優化不是早期優化,而是「架構層級選對資料結構與 API,熱路徑微調」。優先消除終結化、最小化轉換、善用實值型別避免裝箱、字串操作採 StringBuilder/string.Create、熱路徑標記 AggressiveInlining、密封類別/方法消除虛擬呼叫、大量配置改用 ArrayPool/Span、非同步熱路徑用 ValueTask。配合 BenchmarkDotNet 持續驗證,方能在 .NET 平台上榨乾硬體效能。