C# IDisposable 和 using的使用小結(jié)
一、IDisposable:顯式釋放資源的契約
1. 為什么要使用IDisposable
先看一個(gè)問題:C# 會(huì)自動(dòng)清理垃圾,為什么還需要手動(dòng)釋放?
C# 的內(nèi)存(比如你 new 出來的對(duì)象)確實(shí)由垃圾回收器(GC)自動(dòng)管理,你不用管。但是程序里還有一些不屬于內(nèi)存的東西,比如:
- 文件(你打開了一個(gè)文件,就占用了操作系統(tǒng)的文件句柄)
- 數(shù)據(jù)庫連接(你連上了數(shù)據(jù)庫,數(shù)據(jù)庫服務(wù)器那邊有資源)
- 網(wǎng)絡(luò)端口(你發(fā) HTTP 請(qǐng)求,系統(tǒng)開了端口)
- 繪圖畫筆(你畫圖時(shí)用的 GDI 對(duì)象)
這些東西,GC 不認(rèn)識(shí),也不會(huì)幫你去關(guān)。如果你不主動(dòng)釋放,它們會(huì)一直存在,導(dǎo)致:
- 文件被你的程序一直占著,別人打不開
- 數(shù)據(jù)庫連接數(shù)爆滿,新用戶連不上
- 程序越跑越慢,最后崩潰
所以,C# 給了你一個(gè)約定:如果你用了這種特殊資源,你用完必須親自歸還。
這個(gè)約定就是 IDisposable。
2. IDisposable 就像一張“歸還資源的標(biāo)簽”
C# 源碼里面的IDisposable 是一個(gè)接口,里面只有一句話:
void Dispose();
任何類(比如 FileStream、SqlConnection)只要實(shí)現(xiàn)了這個(gè)接口,就等于貼了個(gè)標(biāo)簽:“我占用了特殊資源,用完請(qǐng)調(diào)用我的 Dispose() 方法歸還”。
FileStream fs = new FileStream("a.txt", FileMode.Open);
// ... 使用文件
fs.Dispose(); // 歸還文件
但是這樣寫有個(gè)隱患:如果中間代碼拋出異常,Dispose() 可能永遠(yuǎn)不會(huì)執(zhí)行,資源照樣泄露。你還得寫 try-finally:
FileStream fs = null;
try{
fs = new FileStream("a.txt", FileMode.Open);// 使用文件
}finally{
if (fs != null) fs.Dispose();
}
太麻煩,而且容易忘。于是 C# 提供了一個(gè)語法糖using 。
3. 什么時(shí)候自己實(shí)現(xiàn) IDisposable
如果你的類里面包含了需要釋放的東西,那么你的類也應(yīng)該實(shí)現(xiàn) IDisposable。
class Logger : IDisposable{
private FileStream logFile; // 這個(gè)需要釋放!
public Logger(string path){
logFile = new FileStream(path, FileMode.Append);
}
public void Write(string msg) {
// 寫日志
}
public void Dispose(){
logFile?.Dispose(); // 釋放掉內(nèi)部的資源
}
}
然后別人用你的 Logger 時(shí):
using (var logger = new Logger("app.log")){
logger.Write("程序啟動(dòng)");
}
// 自動(dòng)釋放文件
原則: 如果你的類里有任何 IDisposable 類型的成員(比如 FileStream、SqlConnection),你就應(yīng)該實(shí)現(xiàn) IDisposable,并在 Dispose() 里調(diào)用它們的 Dispose()。
二、using 語句:優(yōu)雅且安全的自動(dòng)釋放
1. using 就是幫你自動(dòng)寫 try-finally 的偷懶工具
using 語句的作用: 出了這個(gè)大括號(hào),自動(dòng)調(diào)用 Dispose(),不管有沒有異常。
基本用法:
using (FileStream fs = new FileStream("a.txt", FileMode.Open)){
// 使用 fs 讀文件
}
// 到這里,自動(dòng) fs.Dispose()
-----------------------------------------
//而上面的using基本用法和下面這段代碼實(shí)現(xiàn)是一樣的
FileStream fs = new FileStream("a.txt", FileMode.Open);
try{
// 使用 fs
}finally{
if (fs != null) fs.Dispose();
}
所以 using 的本質(zhì) = 自動(dòng) + 安全的資源歸還
2. 如果用 using 包多個(gè)資源
等價(jià)于嵌套的 try-finally,保證兩個(gè)都被釋放。
using (FileStream input = File.OpenRead("1.txt"))
using (FileStream output = File.OpenWrite("2.txt"))
{
// 復(fù)制文件內(nèi)容
}
// 先自動(dòng)釋放 output,再釋放 input
3. C# 8 之后更簡(jiǎn)單的寫法:using 聲明
using var fs = new FileStream("a.txt", FileMode.Open);
// 使用 fs
// 方法結(jié)束時(shí)會(huì)自動(dòng) Dispose()
效果: fs 的生命周期持續(xù)到方法結(jié)束,而不是大括號(hào)結(jié)束。適合那些“在整個(gè)方法里都要用,最后一起釋放”的場(chǎng)景。
void ReadFile(){
using var fs = new FileStream("a.txt", FileMode.Open);
// 在這里使用 fs
// 方法是這里結(jié)束,fs 自動(dòng)釋放
}
4. 延遲釋放與手動(dòng)控制生命周期
有些場(chǎng)景你不想在代碼塊結(jié)束立刻釋放,比如把對(duì)象返回給調(diào)用方使用。這時(shí)就不能在方法內(nèi) using。而在創(chuàng)建資源的責(zé)任交給調(diào)用方,調(diào)用方負(fù)責(zé) using。
// 不負(fù)責(zé)釋放
public Stream GetStream(){
return new FileStream("data.bin", FileMode.Open);
}
// 調(diào)用方:
using (var stream = GetStream()){
// 調(diào)用方負(fù)責(zé)釋放
}
四、Dispose 模式
如果你寫一個(gè)類,內(nèi)部只包含托管資源(例如另一個(gè) IDisposable 對(duì)象),只需要:
public class MyClass : IDisposable{
private SqlConnection conn;
public void Dispose() => conn?.Dispose();
}
這完全正確,對(duì)于大多數(shù)日常業(yè)務(wù)代碼已經(jīng)足夠。但是,當(dāng)你面對(duì)以下情況時(shí),簡(jiǎn)單的 Dispose 就不夠了:
- 類直接持有非托管資源(比如通過 IntPtr 獲得的文件句柄、GDI 對(duì)象、原生內(nèi)存)
- 類可能被繼承,派生類也可能有自己的資源需要釋放
- 用戶可能忘記調(diào)用 Dispose,導(dǎo)致非托管資源泄漏
為了同時(shí)解決這三個(gè)問題,.NET 設(shè)計(jì)了一個(gè)經(jīng)典的 Dispose 模式(也叫“可釋放模式”),它包含:
- Dispose() 公共方法
- protected virtual void Dispose(bool disposing) 方法
- 終結(jié)器(析構(gòu)函數(shù))
- GC.SuppressFinalize(this)
Dispose 模式 其實(shí)在實(shí)際開發(fā)并不常見,這里詳情就懶得寫了
Dispose 模式 = 一個(gè) Dispose() 方法 + 一個(gè)受保護(hù)的 Dispose(bool) 虛方法 + 一個(gè)終結(jié)器(僅當(dāng)有非托管資源時(shí))。
到此這篇關(guān)于C# IDisposable 和 using的使用小結(jié)的文章就介紹到這了,更多相關(guān)C# IDisposable 和 using內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#中派生類調(diào)用基類構(gòu)造函數(shù)用法分析
這篇文章主要介紹了C#中派生類調(diào)用基類構(gòu)造函數(shù)用法,實(shí)例分析了派生類調(diào)用基類構(gòu)造函數(shù)的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-04-04
淺談c#表達(dá)式樹Expression簡(jiǎn)單類型比較demo
下面小編就為大家?guī)硪黄獪\談c#表達(dá)式樹Expression簡(jiǎn)單類型比較demo。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
C#實(shí)現(xiàn)復(fù)制數(shù)據(jù)庫 C#將A數(shù)據(jù)庫數(shù)據(jù)轉(zhuǎn)到B數(shù)據(jù)庫
這篇文章主要為大家詳細(xì)介紹了C#復(fù)制數(shù)據(jù)庫,將數(shù)據(jù)庫數(shù)據(jù)轉(zhuǎn)到另一個(gè)數(shù)據(jù)庫,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11
C#根據(jù)http和ftp圖片地址獲取對(duì)應(yīng)圖片
這篇文章主要為大家詳細(xì)介紹了C#根據(jù)http和ftp圖片地址獲取對(duì)應(yīng)圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
WinForm中KeyDown,KeyPress和KeyUp的順序與區(qū)別解析
這篇文章主要介紹了WinForm中KeyDown,KeyPress和KeyUp的順序與區(qū)別解析,對(duì)C#初學(xué)者來說很有學(xué)習(xí)借鑒價(jià)值,需要的朋友可以參考下2014-08-08

