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

