BC42324-在 Lambda 運算式中使用反覆運算變數可能會產生非預期的結果

  • 1798
  • 0

BC42324-在 Lambda 運算式中使用反覆運算變數可能會產生非預期的結果

dotBlogs 的標籤:,,

最近有個專案編譯時出現以下的警告:

warning BC42324: 在 Lambda 運算式中使用反覆運算變數可能會造成未預期的結果。請改為在迴圈內建立區域變數,並指派它反覆運算變數的值。

以下是發生編譯警告的程式碼(以主控台專案做測試):


    Sub Main()
        Dim dtVip As New DataTable
        dtVip.Columns.Add("VipId", GetType(Integer))
        dtVip.Rows.Add(2)
        dtVip.Rows.Add(5)
        dtVip.Rows.Add(8)
        Dim UserList As New Dictionary(Of Integer, Func(Of String))
        For i As Integer = 1 To 10
            '在 Where 中的 = i 觸發 BC42324 編譯警告
            If dtVip.AsEnumerable().Where(Function(v) v.Field(Of Integer)("VipId") = i).Any() Then
                UserList.Add(i, Function() If(i Mod 2 = 0, "是Vip", "一般使用者"))
            End If
        Next
        For Each x In UserList
            Console.WriteLine("{0} - {1}", x.Key, x.Value()())
        Next
        Console.ReadLine()
    End Sub
End Module

在 For 迴圈中,Where 中的 = i 觸發 BC42324 編譯警告,而且輸出的結果也挺意外的(因為只是編譯警告,所以還是可以執行):

 142042

明明 2 和 8 這兩個使用者,應該是 VIP,卻變成一般使用者,在正式程式中,這個結果不是拿來輸出,而是做為後續其他流程的參數,所以兩個 VIP 在整個流程中,都用一般使用者的權限在跑流程,幸好後來發現這個警告,要求修正時,才發現和預期結果不一致。

開發人員的修正方式是在 For 迴圈之前,定義變數 i,編譯時警告就消失了:

142421

但其實輸出結果還是錯的!

其實在 MSDN 有 BC42324 編譯警告的說明:

http://msdn.microsoft.com/zh-tw/library/bb763133.aspx

不過 MSDN 沒寫到原因,我自己看 MSDN Code 和我們發生問題的 Code,得到的結論是:因為在 UserList Dictionary 中,Value 的委派方法,其實是在後面 For Each 輸出時(實際調用時)才會真的執行,這時變數 i 已經是迴圈結束後的值,也就是 11,所以 DataTable 中的三筆資料,都是用 11 去做判斷,導致都變成一般使用者。

因為自己推論其實也沒有很有信心,就再爬文研究一下,看到文末列的連結文章,和我自己的推論差不多,不過他寫的更詳細,總之,重點是編譯時,所有迴圈區塊中的變數,依情況,可能會有兩種不同的 scopes (不知道該怎麼翻譯,生命週期?):

  1. 迴圈每次列舉時,會建立一個新的實例(Instance)。
  2. 整個迴圈一開始列印,建立一個實例後,一直到迴圈結束,都不會被被 Dispose 掉。

所以所以上述有警告的狀況,和開發人員改過的版本,都是狀況二:整個迴圈中,i 都是指向同一個實例。依 MSDN 和參考文章的說明,其實正確應該是要在迴圈中,用另一個變數去接,就可以把 scope 換成第一種狀況了:

144341

參考:Closures in VB Part 5: Looping

Many users are surprised to find out the above will print "4 4 4 4 ".  The reason goes back to my previous 2 posts on variable lifetime and scope.  All "For" and "For Each" blocks in Vb have 2 scopes.

  1. Scope where iteration variables are defined
  2. Body of the for loop

The first scope is entered only once no matter how many times the loop is executed.  The second is entered once per iteration of the loop.  Any iteration variables that are defined in a For/For Each loop are created in the first scope (in this case "i" and "cur").  Hence there is only one of those variables for every loop iteration and the lambda function lifts the single variable "i".

--------
沒什麼特別的~
不過是一些筆記而已