天坑,這樣一個lambda隨機取數(shù)據(jù)也有Bug
前幾天,一位網友跟我說他編寫的一段很簡單的代碼遇到了奇怪的Bug,他要達到的效果是從一個List中隨機取出來一條數(shù)據(jù),代碼如下:
第2行代碼生成了一個包含10個User對象的List,這些User的Id值從0遞增到9;第3行代碼中調用List的Find方法來根據(jù)lambda表達式來查找一條數(shù)據(jù),這里通過random.Next()來獲取一個[0,10)之間的隨機數(shù),然后用這個隨機數(shù)來和Id進行比較。按照邏輯來講,F(xiàn)ind一定可以找到一條數(shù)據(jù),所以在第4行代碼中斷言user一定不為null。但是這段代碼有的時候運行正常,有的時候則會斷言失敗,從而程序拋出異常,令人不解。
當然,他的這段代碼寫的過于復雜,其實改成users[random.Next(0, 10)]就簡單又高效。但是為了揭示問題的本質,我這里繼續(xù)分析為什么用Find+lambda方法會出現(xiàn)問題。
我們查看一下Find方法的源代碼,如下:
Find方法的邏輯很簡單,就是遍歷List中的數(shù)據(jù),對于每條數(shù)據(jù)都調用match這個委托來判斷當前這條數(shù)據(jù)是否滿足條件,如果找到一條滿足條件的數(shù)據(jù),就把它返回。如果走到最后都沒有找到,就返回默認值(比如null)。這個邏輯簡單到貌似看不到任何問題。
問題的關鍵就在if (match(_items[i]))這一句代碼。它是在每一次循環(huán)都調用一下match的委托來判斷當前數(shù)據(jù)的匹配性。而match指向的委托的方法體是p => p.Id == random.Next(0, 10),也就是每次匹配判斷都要獲取一個新的隨機數(shù)來進行比較。假設在循環(huán)的時候生成的10個隨機數(shù)為:9,8,8,7,9,1,1,2,3,4,那么就會每次match(_items[i])判斷的結果都為false,從而導致最后返回null,也就是找不到任何的數(shù)據(jù)。
明白了原理之后,解決這個問題的思路就是不要在lambda中生成待比較的隨機數(shù),而是提前生成隨機數(shù),代碼如下:
同樣的原理也適用于Single()、Where()等LINQ操作。在這些操作中也要避免在lambda表達式中再進行復雜的計算,這樣不僅可以避免類似這篇文章中提到的bug,而且可以提升程序的運行效率。
歡迎閱讀我編寫的《ASP.NET Core技術內幕與項目實戰(zhàn)》,這本書的宗旨就是“講微軟文檔中沒有的內容,講原理、講實踐、講架構”。
