在C#中生成隨機數
[[academy-video-youtube({"vid": "GFLPncbEi-w", "start_time": "0", "title": "Generating Random Numbers in C#", "creator": "Tim Corey", "length": "10m 05s"})]]
在C#中生成隨機數看起來應該是一行程式碼,在許多情況下確實如此。 不過,該語言提供了不止一種生成隨機值的方法,當您考慮到執行緒安全性、可重現性和使用情況時,它們之間的差異就變得重要。 選擇錯誤的方法可能會在多執行緒程式碼中引入細微的錯誤,或使報告的缺陷無法重現。
在他的影片《在C#中生成隨機數》中,Tim Corey逐步說明經典的Random.Shared作為現代預設選擇。 我們會介紹每種方法及其背後的原因,讓您不必猜測就能選擇正確的方式。 如果您曾經想知道為什麼似乎如此簡單的事情會有多條路徑,這篇文章將為您分析解析。
設置演示環境
[0:11 - 0:59] Tim在Visual Studio 2026(目前為預覽版)運行的控制台應用程式中工作,使用.NET 10。他指出這裡展示的一切同樣適用於.NET 9和Visual Studio 2022,因此您可以使用已安裝的工具跟著一起操作。
演示佈局是一個for迴圈,每次迭代側邊並列列印兩個隨機值。 將兩個輸出並行運行使得更容易觀察兩個生成器是否獨立運作或是否產出匹配的結果,當種子值參與時,這一區別變得重要。
經典的Random類
[0:59 - 3:28] 在C#中生成隨機整數最初的方法涉及建立Random類的一個實例:
Random rng1 = new Random();
Random rng2 = new Random();Random rng1 = new Random();
Random rng2 = new Random();每個實例會維護自己的內部狀態。 在任何一個上調用100。
int output1 = rng1.Next(1, 101);
int output2 = rng2.Next(1, 101);int output1 = rng1.Next(1, 101);
int output2 = rng2.Next(1, 101);運行應用程式確定兩個實例產生不同的序列。 這個結果看起來直觀,但當兩個實例共享同一個起始點時會產生不同的故事。
此方法的一個重要警告:個別的Random實例是不執行緒安全的。 如果您的應用程式執行並行處理且多個執行緒存取同一個實例,則內部狀態可能會損壞,產生零或重複的值。 安全的做法是為每個執行緒建立一個實例。 該限制是語言後來引入更好替代方案的原因之一。
種子值和可重現的序列
[3:28 - 6:00] 然後Tim將一個明確的種子傳遞給兩個構造器:
Random rng1 = new Random(25);
Random rng2 = new Random(25);Random rng1 = new Random(25);
Random rng2 = new Random(25);輸出發生驚人的變化。 現在兩個生成器產生完全相同的序列:79, 16, 25, 90, 50, 41, 等等。 如果您不知道種子,這些數字仍然是單個不可預測的,但給定相同的起始值,進程是確定性的。
為什麼有人會想要這樣呢? Tim給出了一個實際的例子。 想像一個遊戲在整個會話中生成隨機事件。 玩家報告了一個錯誤,但由於結果是隨機的,重現它似乎是不可能的。 如果遊戲記錄了該會話使用的種子,開發者可以通過以相同值初始化一個新的Random實例來重新建立確切的決定鏈。 同樣的邏輯也適用於單元測試場景,您需要一致的輸出來對隨機行為寫出可靠的斷言。
種子實例為您提供受控的隨機性:序列看似不可預測,但可以按需重播。 這種能力是接受種子的經典Random構造函式尚未被廢棄的原因,即使現在有更簡單的API。
Random.Shared:現代預設
[7:36 - 9:01] 從.NET 6開始,最適合大多數隨機數生成的推薦方法是Random.Shared:
int output1 = Random.Shared.Next(1, 101);
int output2 = Random.Shared.Next(1, 101);int output1 = Random.Shared.Next(1, 101);
int output2 = Random.Shared.Next(1, 101);不涉及實例化。 Random.Shared 是運行時管理的靜態、執行緒安全實例。您調用Random類上的任何其他方法)並接收一個值,而不必擔心物件的生命週期或並發性。
Tim運行了演示兩次來證明這一點。 第一次執行以94和91開始; 第二次以42和70開始。與種子實例不同的是,Random.Shared每次進程啟動時都是從不同的初始狀態中選取的。 您無法設置種子,這意味着您無法通過此API生成可重現的序列。 這就是折衷:簡單性和安全性以放棄確定性重播為代價。
除了Random.Shared還展示了生成double型數、填充字節陣列和混洗集合的方法。 對於絕大多數需要快速隨機值的應用程式程式碼,這個單一的靜態屬性取代了管理自己實例的樣板。
選擇正確的方法
[9:01 - 9:30] Tim以簡潔的決策框架結束。 對於日常隨機(選擇一個值,混洗一個清單,選取一個隨機元素),Random.Shared是正確的選擇。 它不需要設定,處理並發性,並在跨執行緒中正確運作。
當您需要可重複的輸出系列時,無論是為了除錯、測試還是模擬重播,請建立具有已知種子的專用Random實例。 請記住,這些實例共享在執行緒間是不安全的。
而且對於涉及安全性的任何內容(令牌、密鑰、密碼鹽),這兩種方法都不合適。 Tim將觀看者引導至System.Security.Cryptography中的加密庫,這些庫生成的不僅是隨機值,還是無法預測的。
總結:簡單的API,意義深遠的差異
[9:30 - 9:50] 這個話題具有欺騙性之處在於程式碼極少。 單行程式碼就可以通過任何這些方法生成一個隨機數。 其複雜性不在於語法,而在於理解每種方法提供的保證:執行緒安全性、可重現性或加密強度。
結論
[9:50 - 10:05] 總結:Random.Shared在零設置和內建執行緒安全下涵蓋了大多數需求。 種子的Random實例可讓您在需要時重現特定序列,無論是除錯或測試需要。 加密生成器屬於涉及安全敏感程式碼中,預測性是一種弱點而不是功能。
下次在C#中需要一個隨機數時,決定的關鍵問題是:你是否需要將此序列重播? 如果答案是否定的,Random.Shared便足矣。
範例提示:在調用101。 這一個偏差的邊界適用於種子實例。

