C#減少代碼運(yùn)行時間的7個實(shí)戰(zhàn)技巧
引言
C# 再次登頂!2025 年 TIOBE 年度編程語言榜單揭曉,C# 摘得桂冠。這已經(jīng)是 C# 三年內(nèi)第二次獲此殊榮。
但語言再好,代碼寫得慢也是白搭。
你是否遇到過這樣的場景:
- 循環(huán)處理 10 萬條數(shù)據(jù),跑了幾分鐘
- 字符串拼接內(nèi)存爆炸
- LINQ 查詢看似優(yōu)雅,實(shí)則性能堪憂
今天我們就來聊聊 7 個實(shí)戰(zhàn)技巧,讓你的 C# 代碼運(yùn)行時間大幅縮短。
一、選對數(shù)據(jù)結(jié)構(gòu):O(n) vs O(1)
錯誤示范
// 用 List 查找,時間復(fù)雜度 O(n)
var list = new List<int> { 1, 2, 3, /* ...10萬條 */ };
if (list.Contains(99999)) { /* ... */ }
正確做法
// 用 HashSet 查找,時間復(fù)雜度 O(1)
var set = new HashSet<int> { 1, 2, 3, /* ...10萬條 */ };
if (set.Contains(99999)) { /* ... */ }
性能對比
| 操作 | List | HashSet | Dictionary |
|---|---|---|---|
| 查找 | O(n) | O(1) | O(1) |
| 插入 | O(1) | O(1) | O(1) |
| 刪除 | O(n) | O(1) | O(1) |
經(jīng)驗(yàn)法則:
- 頻繁查找 → HashSet / Dictionary
- 需要索引訪問 → List
- 需要排序 → SortedSet / SortedDictionary
二、字符串拼接:告別 String,擁抱 StringBuilder
為什么 String 慢
String 是不可變對象。每次拼接都會創(chuàng)建新對象:
string s = "a"; s += "b"; // 創(chuàng)建新對象 "ab",舊對象 "a" 等待 GC s += "c"; // 創(chuàng)建新對象 "abc",舊對象 "ab" 等待 GC
循環(huán)拼接 10000 次?恭喜,你創(chuàng)建了 10000 個字符串對象。
StringBuilder 登場
var sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
sb.Append(i);
}
string result = sb.ToString();
性能對比
// 測試:拼接 10000 次字符串 // String: 約 150ms string s = ""; for (int i = 0; i < 10000; i++) s += i; // StringBuilder: 約 1ms var sb = new StringBuilder(); for (int i = 0; i < 10000; i++) sb.Append(i);
150 倍性能差距,這就是選對工具的力量。
三、Span:零拷貝內(nèi)存操作
什么是 Span
Span 是 C# 7.2 引入的高性能內(nèi)存結(jié)構(gòu),它:
- 指向連續(xù)內(nèi)存區(qū)域
- 無需復(fù)制數(shù)據(jù)
- 棧分配,無 GC 壓力
傳統(tǒng)方式 vs Span
// 傳統(tǒng)方式:截取子字符串會分配新內(nèi)存 string text = "Hello, ManongGangzi!"; string sub = text.Substring(0, 5); // 新分配 "Hello" // Span 方式:零分配 ReadOnlySpan<char> span = text.AsSpan(); ReadOnlySpan<char> subSpan = span.Slice(0, 5); // 無分配
實(shí)戰(zhàn)案例:解析數(shù)字
// 傳統(tǒng)方式 string numStr = "12345"; int num = int.Parse(numStr); // 已分配的字符串 // Span 方式 ReadOnlySpan<char> span = "12345".AsSpan(); int num = int.Parse(span); // 直接解析,無需中間字符串
適用場景
- 高頻字符串處理
- 大數(shù)組切片操作
- 網(wǎng)絡(luò)數(shù)據(jù)包解析
- 游戲開發(fā)中的緩沖區(qū)操作
四、異步編程:不要阻塞線程
錯誤示范:同步阻塞
// 阻塞主線程 var result = httpClient.GetStringAsync(url).Result;
這會導(dǎo)致:
- 線程池饑餓
- 應(yīng)用響應(yīng)變慢
- 死鎖風(fēng)險
正確做法:async/await
// 非阻塞,釋放線程
public async Task<string> GetDataAsync(string url)
{
return await httpClient.GetStringAsync(url);
}
并行處理多個任務(wù)
// 串行:總時間 = sum(每個任務(wù)時間)
var data1 = await GetDataAsync(url1);
var data2 = await GetDataAsync(url2);
var data3 = await GetDataAsync(url3);
// 并行:總時間 = max(每個任務(wù)時間)
var tasks = new[]
{
GetDataAsync(url1),
GetDataAsync(url2),
GetDataAsync(url3)
};
var results = await Task.WhenAll(tasks);
3 個請求各 1 秒:
- 串行:3 秒
- 并行:1 秒
五、LINQ 陷阱:延遲執(zhí)行 vs 立即執(zhí)行
延遲執(zhí)行的坑
var numbers = new List<int> { 1, 2, 3 };
var query = numbers.Where(n => n > 1); // 此時未執(zhí)行
numbers.Add(4); // 修改源數(shù)據(jù)
var result = query.ToList(); // 這里才執(zhí)行!
// result = [2, 3, 4],包含了后添加的 4
多次枚舉的性能問題
var query = numbers.Where(n => n > 1);
// 錯誤:每次都重新執(zhí)行
if (query.Any()) // 執(zhí)行一次
{
var first = query.First(); // 又執(zhí)行一次
var count = query.Count(); // 再執(zhí)行一次
}
// 正確:緩存結(jié)果
var list = numbers.Where(n => n > 1).ToList();
if (list.Any())
{
var first = list.First(); // 從緩存讀取
var count = list.Count(); // 從緩存讀取
}
性能對比
| 操作 | 延遲執(zhí)行 | 立即執(zhí)行 (ToList) |
|---|---|---|
| 多次訪問 | 每次重新計(jì)算 | 一次計(jì)算,多次讀取 |
| 內(nèi)存占用 | 低 | 需要存儲結(jié)果 |
| 適用場景 | 單次遍歷 | 多次訪問 |
六、避免裝箱拆箱
什么是裝箱拆箱
int value = 42; object obj = value; // 裝箱:值類型 → 引用類型 int back = (int)obj; // 拆箱:引用類型 → 值類型
每次裝箱都會:
- 在堆上分配內(nèi)存
- 觸發(fā) GC
錯誤示范
var list = new ArrayList(); // 非泛型集合
list.Add(1); // 裝箱
list.Add(2); // 裝箱
int sum = 0;
foreach (int i in list) // 拆箱
{
sum += i;
}
正確做法
var list = new List<int>(); // 泛型集合
list.Add(1); // 無裝箱
list.Add(2); // 無裝箱
int sum = 0;
foreach (int i in list) // 無拆箱
{
sum += i;
}
性能差異
循環(huán) 100 萬次:
- ArrayList:約 50ms
- List:約 5ms
10 倍差距,這就是泛型的威力。
七、對象池:復(fù)用而非重建
為什么需要對象池
頻繁創(chuàng)建銷毀對象會:
- 增加 GC 壓力
- 觸發(fā)頻繁 GC 暫停
- 影響應(yīng)用響應(yīng)時間
使用 ArrayPool
using System.Buffers;
// 傳統(tǒng)方式:每次分配新數(shù)組
byte[] buffer = new byte[1024];
ProcessData(buffer);
// 對象池方式:復(fù)用數(shù)組
var pool = ArrayPool<byte>.Shared;
byte[] buffer = pool.Rent(1024);
try
{
ProcessData(buffer);
}
finally
{
pool.Return(buffer); // 歸還池中
}
使用 MemoryPool
using System.Buffers; var pool = MemoryPool<byte>.Shared; using var memoryOwner = pool.Rent(1024); ProcessData(memoryOwner.Memory); // 自動歸還
適用場景
- 高頻臨時數(shù)組
- 網(wǎng)絡(luò)緩沖區(qū)
- 游戲中的臨時對象
- 圖像處理中的像素緩沖
總結(jié):7 個優(yōu)化清單
| 技巧 | 收益 | 適用場景 |
|---|---|---|
| 選對數(shù)據(jù)結(jié)構(gòu) | O(n) → O(1) | 頻繁查找 |
| StringBuilder | 150x 提升 | 字符串拼接 |
| Span | 零拷貝 | 內(nèi)存操作 |
| async/await | 并行加速 | I/O 操作 |
| LINQ ToList | 避免重復(fù)計(jì)算 | 多次訪問 |
| 泛型集合 | 10x 提升 | 避免裝箱 |
| 對象池 | 減少 GC | 高頻創(chuàng)建 |
寫在最后
性能優(yōu)化不是一蹴而就的,而是點(diǎn)滴積累。
記住三個原則:
- 先測量,再優(yōu)化 —— 用 BenchmarkDotNet 找到真正的瓶頸
- 選對工具 —— 數(shù)據(jù)結(jié)構(gòu)、API、設(shè)計(jì)模式
- 避免過早優(yōu)化 —— 可讀性優(yōu)先,性能瓶頸出現(xiàn)時再優(yōu)化
到此這篇關(guān)于C#減少代碼運(yùn)行時間的7個實(shí)戰(zhàn)技巧的文章就介紹到這了,更多相關(guān)C#減少代碼運(yùn)行時間內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實(shí)現(xiàn)翻轉(zhuǎn)字符串的方法
這篇文章主要介紹了C#實(shí)現(xiàn)翻轉(zhuǎn)字符串的方法,涉及C#操作字符串的技巧,非常具有實(shí)用價值,需要的朋友可以參考下2015-04-04
C#實(shí)現(xiàn)將Excel工作表拆分為獨(dú)立文件
在日常數(shù)據(jù)處理中,你是否經(jīng)常遇到需要將一個包含多個工作表的Excel文件,根據(jù)每個工作表的內(nèi)容,分別保存成獨(dú)立的Excel文件,下面小編就來和大家講講如何使用C#實(shí)現(xiàn)這一功能吧2025-12-12
詳解C# ConcurrentBag的實(shí)現(xiàn)原理
ConcurrentBag<T>實(shí)現(xiàn)了IProducerConsumerCollection<T>接口,該接口主要用于生產(chǎn)者消費(fèi)者模式下,可見該類基本就是為生產(chǎn)消費(fèi)者模式定制的。然后還實(shí)現(xiàn)了常規(guī)的IReadOnlyCollection<T>類,實(shí)現(xiàn)了該類就需要實(shí)現(xiàn)IEnumerable<T>、IEnumerable、 ICollection類2021-06-06
C#實(shí)現(xiàn)串口通信的四種靈活策略和避坑指南
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)串口通信的四種靈活策略和避坑的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2026-01-01
兩路歸并的數(shù)組與鏈表的實(shí)現(xiàn)方法
本篇文章對兩路歸并的數(shù)組與鏈表的實(shí)現(xiàn)方法進(jìn)行了分析介紹。需要的朋友參考下2013-05-05
C#先判斷是否存在再創(chuàng)建文件夾或文件與遞歸計(jì)算文件夾大小
這篇文章介紹了C#先判斷是否存在再創(chuàng)建文件夾或文件與遞歸計(jì)算文件夾大小的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07

