Collection was modified; enumeration operation may not execute - vb.net

I cannot get to the bottom of this error because it happens only in one instance, and I can't find any code that can be the cause of the error.
I have a 3.5 web service that I'm calling from a multi-threaded, CAB client. I have a bunch of unit tests against the web service (from both 3.5 and 2.0 code), and it works fine. However, in the actual application, it doesn't work 90% of the time and, the remaining 10% of the time, it decides to work.
The code:
Friend Function ExecuteSearch(ByVal query As String) As List(Of SomeObject)
Dim searchResults As List(of Object) = _searcher.UserSearch(query)
Return searchResults
End Function
// In Searcher
Public Function UserSearch(ByVal query As String) As List(Of SomeObject)
Return Translate(Search.GetResults(query))
End Function
// In Search
Public Function GetResults(ByVal query As String) As List(Of SomeObject)
Dim service As New FinderService.FinderService()
Dim results As New List(Of String)
Dim serviceResults As IEnumerable(Of String) = service.Search(query) // <-- ERRORS OUT HERE
results.AddRange(serviceResults)
Return results
End Function
// In the service
Public Function Search(ByVal query As String) As IEnumerable(Of String)
Initialize() // Initializes the _accounts variable
Dim results As New List(of String)
For Each account As User In _accounts
If a bunch of conditions Then
results.Add(account.Name)
End IF
End For
Return results
End Function
The breakpoints hit these codes (in this order). The line that errors out is in the "GetResults" method.
Any help would be appreciated.

Ah, the Heisenbugs :D
Apparently _accounts get modified during the loop. You can alleviate it by doing
For Each account As User In _accounts.ToList()
so a copy of current _accounts is created and enumerated and not the actual collection that might change

Related

Can't get a list of declared methods in a .net class

Having read a great many posts on using reflection to get a list of methods in a given class, I am still having trouble getting that list and need to ask for help. This is my current code:
Function GetClassMethods(ByVal theType As Type) As List(Of String)
Dim methodNames As New List(Of String)
For Each method In theType.GetMethods()
methodNames.Add(method.Name)
Next
Return methodNames
End Function
I call this method like this:
GetClassMethods(GetType(HomeController))
The return has 43 methods, but I only want the methods I wrote in the class. The image below shows the beginning of what was returned. My declared methods are in this list, but down at location 31-37. There are actually 9 declared methods, but this list doesn’t show the Private methods.
When I look at theType, I see the property I want. It is DeclaredMethods which shows every declared method, public and private.
However, I’m not able to access this property with a statement such as this.
Dim methodList = theType.DeclaredMethods
The returned error is that DelaredMethods is not a member of Type. So, my questions are multiple:
1) Most important, what code do I need to retrieve every declared method in the class, and only the methods I declared?
2) Why am I not able to access the property that gives the list of DeclaredMethods()?
Try this:
Function GetClassMethods(ByVal theType As Type) As List(Of String)
Dim flags = Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.DeclaredOnly
Dim result = theType.GetMethods(flags).
Where(Function(m) Not m.IsSpecialName).
Select(Function(m) m.Name)
Return result.ToList()
End Function
or for some fun with generics:
Function GetClassMethods(Of T)() As List(Of String)
Dim flags = Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.DeclaredOnly
Dim result = GetType(T).GetMethods(flags).
Where(Function(m) Not m.IsSpecialName).
Select(Function(m) m.Name)
Return result.ToList()
End Function
The IsSpecialName filter excludes methods with compiler-generated names, such as the special methods used by the compiler to implement properties. You can also play around more with the flags if you need to include, say, NonPublic members as well.
Finally, whenever you have a method ending with Return something.ToList() (or which could end with it, as my adaption shows here), it's almost always better to change the method to return an IEnumerable(Of T) instead, and let the calling code call ToList() if it really needs it. So my first example above is really better like this:
Function GetClassMethods(ByVal theType As Type) As IEnumerable(Of String)
Dim flags = Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.DeclaredOnly
Return theType.GetMethods(flags).
Where(Function(m) Not m.IsSpecialName).
Select(Function(m) m.Name)
End Function
Hey, that could be a single-liner. Then for those situations where you really need a list, you can do this:
Dim methodNames As List(Of String) = GetClassMethods(HomeController).ToList()
You'll start to find in many situations you don't need to use ToList() at all; the generic IEnumerable was good enough. Certainly this is true anywhere you just use the result with For Each loop. Now suddenly the memory use in your programs are significantly reduced.

VB.Net Async - Check large list of string for a match

I need this function to run Async, but can't seem to figure a way to do it.
LIST1 is Public and contains a List(of String) with a few hundred entries. List Declaration:
Public LIST1 As New List(Of String)
Normally, I'd run the following code to retrieve the boolean of whether or not he list contains the entry:
Dim result = LIST1.Any(Function(s) value.ToLower.Contains(s))
Full non-Async function:
Function CheckValue(byval value As String) As Boolean
Dim result As Boolean = LIST1.Any(Function(s) value.ToLower.Contains(s))
Return result
End Function
That works well as expected.
How would I implement the same as an Async function? I've tried:
Async Function CheckValue(byval value as String) As Task(Of Boolean)
Dim result as Task(Of Boolean) = Await LIST1.Any(Function(s) value.ToLower.Contains(s))
Return result
End Function
I get the following error: 'Await' requires that the type 'Boolean' have a suitable GetAwaiter method.
Any thoughts?
It does not return a task, so there is no reason to await it. If your concern is that it is too slow, you can run any synchronous code in a new thread, and then await the completion of that thread, like this:
Dim result As Boolean = Await Task.Run(Function() LIST1.Any(Function(s) value.ToLower.Contains(s)))
Or, as GSerg mentioned, though it technically doesn't make it awaitable, you can use AsParallel.Any:
Dim result As Boolean = LIST1.AsParallel.Any(Function(s) value.ToLower.Contains(s))
However, be aware that starting new threads has a fair amount of overhead, so starting up a new thread, just to do a small amount of work, may make it actually run slower.
In this particular case, if performance is key, I would recommend looking into various search/indexing algorithms. For instance, take a look at some of the ones mentioned here. I wouldn't be surprised if there are opensource .NET libraries for those kinds of algorithms.

SyncLock not working in unit tests

I've got a Module that I'm wanting to use to cache some stuff. It's pretty simple. I wanted to shy away from the ConcurrentDictionary because it needs to be a guaranteed operation.
Public Module SchemaTableCache
Private lockObject As New Object
Private columnCache As New Dictionary(Of String, SortedSet(Of String))
<Extension>
Public Sub CacheSchemaTable(dataReader As IDataReader, name As String)
SyncLock lockObject
Dim rows As New List(Of DataRow)
If columnCache.ContainsKey(name) Then
Return
End If
rows = dataReader.GetSchemaTable().Rows.OfType(Of DataRow)().ToList()
columnCache.Add(name, New SortedSet(Of String)(rows.Select(Function(r) r.Field(Of String)("ColumnName"))))
End SyncLock
End Sub
<Extension>
Public Function HasColumn(name As String, column As String) As Boolean
SyncLock lockObject
Dim cols As New SortedSet(Of String)
If Not columnCache.TryGetValue(name, cols) Then
Return False
End If
Return cols.Contains(column)
End SyncLock
End Function
End Module
Here's the thing. I have some unit tests that test the code that leverages the HasColumn function. I set these tests up like this:
dataReader.Setup(Function(x) x(field)).Returns(val)
' setup the schema table
Dim table As New DataTable()
table.Columns.Add("ColumnName", GetType(String))
If setupTable Then
table.Rows.Add(field)
End If
dataReader.Setup(Function(x) x.GetSchemaTable()) _
.Returns(table)
dataReader.Object.CacheSchemaTable("table")
Then they test this function:
Dim typeName = GetType(T).Name
Debug.WriteLine($"IDataReader_Value({schemaTableName}.{column})")
If Not schemaTableName.HasColumn(column) Then
Debug.WriteLine($"Could not find column {column}; returning default value.")
Return typeName.DefaultValue()
End If
Dim input = dr(column)
Debug.WriteLine($"Found column {column}; returning value {input}.")
Return Value(Of T)(input)
You can see here where I hit the HasColumn method. Here's the thing. If I execute these tests individually they succeed; however, they fail if I execute the entire set of tests.
Clearly there is a thread-safety issue here, but I can't for the life of me figure out what I did wrong. Can somebody help me see where I went wrong?
The output of a test when it's failing is:
Test Name: IDataReader_ValueBoolean
Test Outcome: Failed
Result Message: Assert.AreEqual failed. Expected:<True>. Actual:<False>.
Result StandardOutput:
Debug Trace:
IDataReader_Value(table.field)
Could not find column field; returning default value.
The output of a test when it succeeds is:
Test Name: IDataReader_ValueBoolean
Test Outcome: Passed
Result StandardOutput:
Debug Trace:
IDataReader_Value(table.field)
Found column field; returning value True.
I figured it out. The issue wasn't with SyncLock, it was just with my logic. Each test is hitting a different problem. Some are testing the missing column, while some are expecting it to exist. Because of this I needed to be able to update the cache.
Here is the new logic:
SyncLock lockObject
Debug.WriteLine($"Caching schema table {name}.")
Dim rows As New List(Of DataRow)
If Not columnCache.ContainsKey(name) Then
Debug.WriteLine($"Adding cache key for {name}.")
columnCache.Add(name, New SortedSet(Of String)())
End If
rows = dataReader.GetSchemaTable().Rows.OfType(Of DataRow)().ToList()
Debug.WriteLine($"Schema table rows count: {rows.Count}")
columnCache(name) = New SortedSet(Of String)(rows.Select(Function(r) r.Field(Of String)("ColumnName")))
Debug.WriteLine($"Successfully cached {name}.")
End SyncLock

problems iterating through generic list of string

I have the follow code in my program where I hit a SQLCe database to append the results into a list. That part works, but instead of exiting the function 'QueryDB' it goes to the else statement and runs the function again, which will return a null value. I designed it this way becuase I wanted to check to make sure the database is open before I try to execute the SQL statement, and if it's not open, call the method to open it and run through the function again.
Public Function QueryDB(ByVal strSQL As String) As List(Of String)
Dim reader As SqlCeDataReader
Dim results As New List(Of String)
Using cmdAdd As New SqlCeCommand(strSQL, conn)
If Me.checkConnection Then
reader = cmdAdd.ExecuteReader()
Do While reader.Read
results.Add(reader.GetString(0))
Loop
Return results
Exit Function
Else
connectPlansdb()
QueryDB(strSQL) 'does not exit function when done and goes through the function again
End If
End Using
End Function
The second problem I have is when I try to populate the list into a combo box in my form class where I call out to the database and use the returned list to populate my combo box. I can't seem to figure out how to get the code to deal with the list.
Private Sub cmbInvestmentStrategy_DropDown(sender As System.Object, e As System.EventArgs) Handles cmbInvestmentStrategy.DropDown
Dim strat As New clsInvestmentStrategies()
Dim invStrat As New List(Of String)
invStrat = strat.getInvestmentStrategies() 'String cannot be converted to List(pf String)
cmbInvestmentStrategy.Items.Add(invStrat) 'Error 3 Value of type 'System.Collections.Generic.List(Of String)' _
'cannot be converted to '1-dimensional array of Object'.
End Sub
Any help would be greatly appreciated.
Thanks!
Your QueryDB method has a big flaw. If the database is unavailable (connectivity problems, database offline or wrong connection string), there will be an infinite loop. Your query DB method should just query the database. You could wrap it in another method responsible for connecting to the database, but you don't want to retry database connection infinitely.
As for your second method:
invStrat = strat.getInvestmentStrategies() 'String cannot be converted to List(Of String)
Error is pretty clear here. getInvestementStrategies returns a single String and cannot be converted to a list. It should return a List(Of String), or at least some collection of strings, I suppose?
cmbInvestmentStrategy.Items.Add(invStrat) 'Error 3 Value of type 'System.Collections.Generic.List(Of String)' _
'cannot be converted to '1-dimensional array of Object'.
Items.Add will add a single element to the combobox. You should loop through the invStrat values, and call Add for every item. Alternatively, you could use the AddRange method, but this method expects an Array, not a List.

How can I copy an object of an unknown type in VB.net?

Rather than giving the very specific case (which I did earlier), let me give a general example. Let's say that I have a function, called callingFunction. It has one parameter, called parameter. Parameter is of an unknown type. Let us then say that I wish to copy this parameter, and return it as a new object. For example, in pseudo code, something along the lines of...
Function callingFunction(ByVal parameter As Object) As Object
Dim newObj As New Object
'newObj has the same value as parameter, but is a distinctly different object
'with a different reference
newObj = parameter
return newObj
End Function
EDIT: Additional Information
The first time I posted this question, I received only one response - I felt that perhaps I made the question too specific. I guess I will explain more, perhaps that will help. I have an ASP page with 10 tables on it. I am trying, using the VB code behind, to come up with a single solution to add new rows to any table. When the user clicks a button, a generic "add row" function should be called.
The difficulty lies in the fact that I have no guarantee of the contents of any table. A new row will have the same contents as the row above it, but given that there are 10 tables, 1 row could contain any number of objects - text boxes, check boxes, etc. So I want to create a generic object, make it of the same type as the row above it, then add it to a new cell, then to a new row, then to the table.
I've tested it thoroughly, and the only part my code is failing on lies in this dynamic generation of an object type. Hence why I asked about copying objects. Neither of the solutions posted so far work correctly, by the way. Thank you for your help so far, perhaps this additional information will make it easier to provide advice?
You can't do this in general. And it won't be a good idea, for example, if parameter is of a type which implements the singleton pattern. If parameter is of a type which supports copying, it should implement the ICloneable interface. So, your function could look like this:
Function MyFunc(ByVal parameter As Object) As Object
Dim cloneableObject As ICloneable = TryCast(parameter, ICloneable)
If Not cloneableObject Is Nothing Then
Return cloneableObject.Clone()
Else
Return Nothing
End If
End Function
You could implement something like this:
Dim p1 As Person = New Person("Tim")
Dim p2 As Object = CloneObject(p1)
Dim sameRef As Boolean = p2 Is p1 'false'
Private Function CloneObject(ByVal o As Object) As Object
Dim retObject As Object
Try
Dim objType As Type = o.GetType
Dim properties() As Reflection.PropertyInfo = objType.GetProperties
retObject = objType.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, Nothing, o, Nothing)
For Each propertyInfo As PropertyInfo In properties
If (propertyInfo.CanWrite) Then
propertyInfo.SetValue(retObject, propertyInfo.GetValue(o, Nothing), Nothing)
End If
Next
Catch ex As Exception
retObject = o
End Try
Return retObject
End Function
Class Person
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Public Sub New()
End Sub
Public Sub New(ByVal name As String)
Me.Name = name
End Sub
End Class
Here's a simple class that will work for most objects (assumes at least .Net 2.0):
Public Class ObjectCloner
Public Shared Function Clone(Of T)(ByVal obj As T) As T
Using buffer As MemoryStream = New MemoryStream
Dim formatter As New BinaryFormatter
formatter.Serialize(buffer, obj)
buffer.Position = 0
Return DirectCast(formatter.Deserialize(buffer), T)
End Using
End Function
End Class