C#靜態變量和方法是否邪惡?Derek Comartin解釋(視頻分解)
在C#軟體開發的世界中,您可能會遇過 static 關鍵字 —— 無論是在 static void Main、static 變數或 static 方法中。 但是,靜態真的總是個好主意嗎? 還是如某些開發者所警告的,在大型應用程序中有危險?
要了解真相,我們將參考Derek Comartin來自CodeOpinion.com的Static Variables & Methods are Evil?詳細影片,他將詳細解釋為何靜態成員可能會成為問題的原因 —— 但並非總是如此。 我們將使用他的範例和時間戳來指導這次深入研究。
什麼使得靜態方法有問題?
在影片的開始,Derek探討了一個名為Is18YearsOrOlder的靜態方法。 此方法接受一個 DateTime birthDate 並檢查某人是否至少18歲。它使用 DateTime.UtcNow 來比較當前日期。 很簡單,對吧?
但是正如Derek指出的,這個方法是非決定性的。 在0:50時,他指出使用 DateTime.UtcNow 意味著該方法將根據您運行它的時間而返回不同的結果。 這在單元測試中是一個重大問題,並導致意外的代碼行為。
在這種情況下,雖然該方法看起來像是一個純粹的函數,但實際上不是。 Derek解釋說,純方法必須每次以相同的參數調用時返回相同的值。 但在這裡,當前日期一直在改變,因此返回值也會改變。
這說明了一個公共靜態方法,雖然方便,但如果依賴於實時數據或應用程式領域狀態,可能會引入副作用。
使靜態方法可測試且可預測
Derek下一個重點是,我們可以通過去除對 DateTime.UtcNow 的依賴來修正非決定性。 相反,Derek展示如何使用靜態類或介面實作來注入時間提供者。 這讓函數變得決定性 —— 每次傳入相同的輸入時,您都會得到相同的輸出。
在他的PlaceOrder類中,他引入了一個假的日期提供者,以便能夠測試是否在星期五處理的訂單獲得50%的折扣。 這避免了將邏輯硬編碼到系統時間,從而使方法更可靠和可測試。
通過隔離行為並避免直接在業務邏輯中引用靜態方法,Derek展示了如何在保持可測試性的同時保留乾淨的代碼。
緊密耦合和靜態方法
Derek在這一點警告說,依賴於靜態方法通常會引入緊密耦合。 如果您直接使用DateTime.UtcNow,您就受限於該實作 —— 您無法覆蓋或假裝它。
這是一個問題,因為像這樣的靜態成員在您的應用程序中是全局的。 如果您的代碼庫大量使用靜態欄位或靜態屬性,那麼改變行為或注入依賴將變得困難,破壞面向對象編程的關鍵原則。
您也失去了靈活性,因為您無法用不同的實作來代替該靜態欄位,像在實例變數或注入服務中那樣。
靜態變數的全局狀態問題
現在Derek將重點轉向靜態變數,這是話題變得嚴肅的地方。
他展示了一個在Global類中使用靜態快取的範例。 他解釋說,靜態變數的最大問題是未知狀態。 在運行時,您無法確定您的靜態欄位是否已被初始化。 這種不可預測性特別危險,當涉及到可變的靜態 int 或 string 名稱時。
當開發者假定該變數僅有一個副本在線程間共享時,並忘記考慮線程安全性,情況會更糟。
線程安全性與多線程代碼中的靜態欄位
Derek提出了另一個問題:在多線程環境中使用靜態變數。 他給出了使用Parallel.For並發地使用的靜態List
為了解決這個問題,他改用ConcurrentBag
他的觀點很明確:如果您在多線程中使用靜態變數,請確保它們是線程安全的。 否則,您的程式可能會不按預期運行甚至崩潰。
安全使用靜態方法
然後Derek分享了一個靜態方法的安全有效用法:一個簡單的實用方法MilesToKilometers。 它接受一個int miles並在轉換後返回一個double值。 這個方法是決定性的 —— 對於相同的int值,您總是會得到相同的結果。
這種方法不依賴非靜態欄位、不會改變共享數據,也不涉及任何未知狀態。 這是一個如何正確使用C#中static關鍵字的絕佳範例。
在.NET上下文中理解靜態
在C#中,static關鍵字可以應用於類、欄位、方法、構造函数和屬性。 Derek間接地觸及以下概念:
-
靜態類:一個無法實例化且只能包含靜態成員的類。
-
靜態欄位:用static關鍵字聲明 —— 每個應用域中僅存在一個副本。
-
靜態構造函數:僅當類首次被訪問時運行一次。
-
靜態void Main:大多數C#應用的入口點,展示靜態方法如何必不可少。
- 靜態int、靜態string:靜態欄位的例子,用於儲存類的所有實例或事實上不需要實例的數據。
與每次創建對象時運行的實例構造函數不同,靜態構造函數只初始化類級資源一次。
這種區別幫助開發者決定何時使用實例變數對比靜態變數,或何時使用屬性存取器來封裝共享成員變數。
Derek的最後要點
Derek總結了開發者為什麼對靜態成員保持警惕的主要原因:
-
緊密耦合 —— 您被靜態方法或欄位的行為所困住。
-
非決定性行為 —— 難以測試,容易破裂。
-
全局可變狀態 —— 您不知道值是什麼或由誰更改的。
- 並發問題 —— 在多線程代碼中對共享數據的不安全訪問。
然而,正如Derek所說,靜態並不壞。 它在正確使用時很有力量 —— 特別是在實用函數、共享常數或真正的全局設定中。 您只需要仔細管理狀態,避免依賴可變或系統特定的行為。
結束語
在C#中,靜態變數和方法是把雙刃劍。 正如Derek Comartin清楚地解釋的,它們本身並不壞 —— 但需要仔細使用。 當您需要共享數據或不依賴於對象狀態的功能時,請使用靜態欄位和靜態類別。 但避免將它們用於依賴時間、系統狀態或需要靈活性的東西。
因此,在創建對象或訪問靜態欄位之前,請考慮範圍、可測試性、線程安全性以及代碼是否需要一個副本或多個實例。
觀看Derek Martin的完整影片 在他的CodeOpinion YouTube頻道上。 您將發現更多有關清潔架構、軟體設計和真實世界C#應用的見解。
