List(of T).RemoveAll is also changing previously cached data that it should not be able to change. Am I crazy?
This has been driving me insane all day and I think I have the problem narrowed down, but it just doesn't make sense how it could be happening.
I have 2 public shared functions and 2 private shared functions. One provides a list of custom objects and one filters the objects based on some parameters in the function call. Both return List(of custom object). The 2 private shared functions are the predicate functions used in the List(of t).RemoveAll function.
Here are the functions simplified:
Public Shared Function NarrowItemList(ByVal OnSaleOnly As Boolean, ByVal InStockOnly As Boolean) As ItemList
Dim iList As New ItemList
iList = GetItemList()
If OnSaleOnly Then
iList.Items.RemoveAll(AddressOf GetOnSaleOnly)
End If
If InStockOnly Then
iList.Items.RemoveAll(AddressOf GetInStockOnly)
End If
Return iList
End Function
Public Shared Function GetItemList() as ItemList
Dim l As New ItemList
If Not Cache("SpecialItemList") Is Nothing Then
l = DirectCast(Cache("SpecialItemList"), ItemList)
Else
Using (Connection)
//Get list here using datareader
End Using
Cache.Insert("SpecialItemList", l, Nothing, DateTime.Now.AddMinutes(30), System.Web.Caching.Cache.NoSlidingExpiration)
End If
Return l
End Function
Private Shared Function GetOnSaleOnly(ByVal baditem As Item.BasicItemInfo) As Boolean
If baditem.OnSale = False Then Return True
End Function
Private Shared Function GetInStockOnly(ByVal baditem As Item.BasicItemInfo) As Boolean
If baditem.Qty = 0 Then Return True
End Function
The GetItemList func开发者_运维百科tion is only called in the NarrowItemList function and no where else in the program. After calling it the first time, the data that GetItemList returns should be cached for 30 minutes.
I have debugged both functions line by line. When the GetItemList is called the first time it returns the correct # of Items, let's say a total count of 100. That list of 100 item objects then gets passed to the NarrowItemList function. Based on my thinking, the data that GetItemList returns should be a total of 100 objects for the next 30 minutes.
Here's where the confusion starts to come in. If I call the NarrowItemList function and specify either of the parameters as True, it then uses the .RemoveAll function and the associated predicate function to narrow the list. This then narrows the list, as it should, to a lower number, say 50 item objects that then get returned from that function. All good so far.
HERE IS WHERE I THINK I AM GOING CRAZY.
Now, the next time I call the GetItemList function it only returns 50 item objects, not the 100 item objects that it should be returning due to the cache being set! I have verified this in the debugger and by running many, many different tests to see what combinations of parameters may be causing the problem. In my testing, as soon as one of the .RemoveAll functions is used the GetItemList item list will get set to whatever the NarrowItemList function returned.
Even if the cache wasn't being set correctly, shouldn't it just get the data again from the database and return 100 items again? What could I be missing???
It isn't clear what cache you are using, but I'll make the assumption that this is the asp.net cache, using default configuration (the same ideas will hold for other in-process caches). If so, this is a reference-preserving cache. Why you put in is the same object that you get back out. This means that if you mutate the cached state, otter callers will see the change; or it could even explode due to cross-threading.
Guidance:
- no not mutate anything you have from cache; make a copy if you need. Perhaps even make the cached data immutable
- or; use a serialising cache (which gives you a clone each time)
When you return the list that is in your cache instead of making a copy of it, you are allowing things to modify the copy in the cache.
When it calls RemoveAll it is removing those items from the list in the cache.
Try something like this: (typed into answer, not tested to compile)
Public Shared Function NarrowItemList(ByVal OnSaleOnly As Boolean, ByVal InStockOnly As Boolean) As ItemList
Dim iList As New ItemList
Dim items2 as IEnumerable(Of Item.BasicItemInfo) = GetItemList().Items
If OnSaleOnly Then items2 = items2.Where(Function(x) x.OnSale)
If InStockOnly Then items2 = items2.Where(Function(x) x.InStock)
iList.AddRange(items2)
Return iList
End Function
Note that you'll have to do even more work if your BasicItemInfo class is something that is not immutable... and arguably some of this work should be done in GetItemList [return a read-only IEnumerable in the first place]
精彩评论