search for an item in collection type in VB .NET 1.1 - vb.net

I am using VB .NET 1.1 and wanted to make sure if inside a key value pair class "Dates" (collection type) a key by name "TerminationDate" exists.
If Not Dates.Item("TerminationDate") Is Nothing Then
'Do x y z'
End if
But this is throwing an exception:
Argument Index is not a valid value.
I am very new to VB.
Thanks

As you saw, the Contains method was added in the 2.0 Framework so you can't use it. As far as I can tell, there is no way within the 1.1 Framework to look for the existence of a given key. The only way to do this is to try getting the item at that key and swallowing the exception if its not found. This helper method will do that:
Private Shared Function CollectionHasKey(col As Microsoft.VisualBasic.Collection, key As String) As Boolean
Try
Dim O = col.Item(key)
Return True
Catch ex As Exception
Return False
End Try
End Function
To use it:
Dim MyCol As New Microsoft.VisualBasic.Collection()
MyCol.Add(New Date(), "D")
Trace.WriteLine(CollectionHasKey(MyCol, "D"))
Trace.WriteLine(CollectionHasKey(MyCol, "Q"))

Item uses the index value to return the item. Its position in the collection starting from 0. If you want to find it using the key: "TerminationDate" You would use Contains instead. Like:
If Dates.Contains("TerminationDate") Then
'Do stuff
End If
Edited based on comments:
I apologize, I thought because you mentioned key/value you pairs that you had used a specific Collection Type. a Dictionary. If you have a Collection of KeyValuePairs you will have to loop thru each item in order to see if the item you want is present. Like:
Dim Item as keyValuePair = nothing
For i as integer = 0 to Dates.Count -1
if Dates.Item(i).Key = "TerminationDate" Then
Item = Dates.Item(i)
End if
Next
If Not Item Is Nothing Then
'Do stuff
End If
My 1.1 type names may be off for keyValuePair, and I think Count is directly off Collection, but it may be a method off of Items (if Items is a property). I don't have the 1.1 framework installed to check.
Contains is a member of dictionary even in 1.1, and would allow you to find an item by key without the loop. here is more information on that type, if that is something you are interested in:
http://msdn.microsoft.com/en-us/library/system.collections.dictionarybase(v=VS.71).aspx

Related

Return values from Dictionary key

I'm a bit of a vb.net noob - apologies if this is a silly question.
I have a collection named Applications and these store objects of type application.
Dim Applications As New Dictionary(Of String, Application)()
Each application is created and added to this collection using
Public Sub New(dbid As String, name As String, status As String, mode As String)
csv_dbid = dbid
csv_app = name
csv_status = status
csv_mode = mode
End Sub
See the image i've included which shows in the debug/output my collection created with the objects and values associated.
I want to know a way I can access a Key and return all the corresponding values of csv_dbid, csv_app, csv_status and csv_mode. I've been googling for a bit and struggling.
Many thanks in advance.
Gary Waddell
In the case of the posted screenshot you would do:
Dim app = Applications("100")
app Will be a HealthCareApplication like any other so you can just use it as you would:
app.csv_dbid = "123"
You can also just refer to the dictionary item without a variable:
Applications("100").csv_dbid = "123"
To find out if the dictionary knows of a key, use the ContainsKey method. This can be particularly useful if you're iterating a colection and want to handle duplicates:
For Each thing in someList
if Applications.ContainsKey(thing.Key) Then
HandleDuplicate(thing)
Else
Applications(thing.Key) = New HealthCareApplication
End if
'It certainly exists in the collection now and this won't crash with KeyNotFound
Applications(thing.Key).csv_dbid = thing.DbId
Next thing
If you enumerate a dictionary you get a collection of KeyValuePair, the Key is (in this case) the "100" you used as the indexer. The Value is a HealthCareApplication type
For Each kvp in Applications
console.Write(kvp.Key)
Console.Write(kvp.Value.csv_dbid)
Next kvp

How Do I loop through this class once I have added items

How do i loop through this class once I add items via this method. Just I am quite new to generic lists so was wonding if someone could point me in right direction in datatables im used to doing the following:
For Each thisentry In dt.rows
Next
What do I use in collections
Calling Code
Calling this in my delciarations of main class
Dim infoNoProductAvail As List(Of infoProductsNotFound) = New List(Of infoProductsNotFound)()
this is how i am adding the files but I have checked in the routine and the count for the list is at 2 products
If medProductInfo.SKU.SKUID = 0 Then
infoNoProductAvail.Add(New infoProductsNotFound(thisenty2.Item("EAN13").ToString(), True))
End If
this is the class itselfs
Public Class infoProductsNotFound
Public Sub New(tbcode As String, notfound As Boolean)
Me.tagbarcode = tbcode
Me.notfound = notfound
End Sub
Private tagbarcode As String = String.Empty
Private notfound As Boolean
Public Property tbcode() As String
Get
Return tagbarcode
End Get
Set(ByVal value As String)
tagbarcode = value
End Set
End Property
Public Property isNotFound() As Boolean
Get
Return notfound
End Get
Set(ByVal value As Boolean)
notfound = value
End Set
End Property
End Class
Tried
I tried using the following
Function BuildExceptionsForEmail()
Dim retval As String = ""
Dim cnt As Int32 = 0
retval = "The following products are not avialable" & vbCrLf
For Each info As infoProductsNotFound In infoNoProductAvail
retval &= info.tbcode
cnt &= 1
Next
Return retval
but for some reason at this point my info noproductAvail is blank even though in the routine above its sitting at count of 2 what gives?
First I'd shrink that declaration a bit:
Dim infoNoProductAvail As New List(Of infoProductsNotFound)
Next, to iterate there are several options. First (and what you're likely most used to):
For Each info as infoProductsNotFound in infoNoProductAvail
If info.tbCode = "xyz" Then
DoSomething(info)
End If
Next
Or you might want to use lambda expressions (if you're using .Net 3.5 and above I think - might be .Net 4):
infoNoProductAvail.ForEach (Function(item) DoSomething(item))
Remember that generics are strongly typed (unlike the old VB collections) so no need to cast whatever comes out: you can access properties and methods directly.
If infoNoProductAvail(3).isNotFound Then
'Do something
End If
(Not that that is a great example, but you get the idea).
The For Each syntax is the same. It works the same way for all IEnumerable objects. The only "trick" to it is to make sure that your iterator variable is of the correct type, and also to make sure that you are iterating through the correct object.
In the case of the DataTable, you are iterating over it's Rows property. That property is an IEnumerable object containing a list of DataRow objects. Therefore, to iterate through it with For Each, you must use an iterator variable of type DataRow (or one of its base classes, such as Object).
To iterate through a generic List(Of T), the IEnumerable object is the List object itself. You don't need to go to one of it's properties. The type of the iterator needs to match the type of the items in the list:
For Each i As infoProductsNotFound In infoNoProductAvail
' ...
Next
Or:
Dim i As infoProductsNotFound
For Each i In infoNoProductAvail
' ...
Next
Or:
For Each i As Object In infoNoProductAvail
' ...
Next
Etc.

Is it possible to do a For...Each Loop Backwards?

I don't believe this is possible by conventional methods, but something like this verbose code:
For Each s As String In myStringList Step -1
//' Do stuff here
Next
I will probably have to invert the myString object before a conventional For..Each Loop, correct?
I think the documentation referenced in Mike's answer below is extremely misleading. The order of For Each is defined by the collection it's called (i.e. its implementation of IEnumerable/IEnumerable<T>), but that's not the same as saying it shouldn't be used when the order is important. Many collections (such as arrays, List<T> etc) always go in the "natural" order.
Part of the documentation does allude to this:
Traversal Order. When you execute a
For Each...Next loop, traversal of the
collection is under the control of the
enumerator object returned by the
GetEnumerator method. The order of
traversal is not determined by Visual
Basic, but rather by the MoveNext
method of the enumerator object. This
means that you might not be able to
predict which element of the
collection is the first to be returned
in element, or which is the next to be
returned after a given element.
That's not at all the same as saying it can't be relied upon - it can be relied upon if you know that the collection you're iterating over will produce the results in the desired order. It's not like it's going to pick elements at random. The behaviour in terms of IEnumerable/IEnumerable<T> is clearly defined on that page.
The most important exceptions to predictable orderings are dictionaries and sets, which are naturally unordered.
To reverse an IEnumerable<T>, use Enumerable.Reverse - but if you need to iterate in reverse over a collection which is indexed by position (such as an array or List<T>) then it would be more efficient to use a For loop starting at the end and working backwards.
You'd have to do something like:
For i as integer = myStringList.Count-1 to 0 step -1
dim s as string = myStringList.Item(i)
' Do your stuff with s
Next i
But as far as I know, you can't do a "For...Each" backwards, though that would be nice in a few instances.
Sadly, the MSDN docs on For Each state that the For Each construct is there explicitly for cases where the order of the iteration is unimportant (unordered sets and the like). So there is unfortunately no concept of reversing a For Each, as the order is not defined anyway.
Good luck!
Call the System.Linq.Enumerable.Reverse method to get an IEnuemrable(Of TSource) in the reverse order of your enumerable source.
Testig in Framework 4, the code
For Each s As String In myStringList.Reverse
//' Do stuff here
Next
it wasn't working, the right way to do it is:
myStringList.Reverse() ' it is a method not a function
For Each s As String In myStringList
//' Do stuff here
Next
Look at: MSDN: Reserve
For Each s As String In myStringList.Reverse
//' Do stuff here
Next
The reason it can't be done is that, as a basic design feature, For Each can iterate over an enumerable without a last element, or with an as yet unknown last element.
This works:
Dim myArray() As String = {"1", "2", "3", "4", "5"}
For Each n As String In myArray.Reverse
Debug.Print(n)
Next
Output: 5 4 3 2 1
'
This also works:
Dim myArray() As String = {"1", "2", "3", "4", "5"}
For i As Integer = myArray.Length - 1 To 0 Step -1
Debug.Print(myArray(i))
Next
Output: 5 4 3 2 1
What you have to do is create an array with your for each you had before, then use array.reverse and run the for each on the array. Done
Cheers
Depending on what happens inside your loop you can do an .InsertAt(object,0) instead of an .Add and produce the same result as a reverse enumeration.
You can add a extended function to the class you are trying to reverse
<Serializable()> Public Class SomeCollection
Inherits CollectionBase
Public Sub New()
End Sub
Public Sub Add(ByVal Value As Something)
Me.List.Add(Value)
End Sub
Public Sub Remove(ByVal Value As Something)
Me.List.Remove(Value)
End Sub
Public Function Contains(ByVal Value As Something) As Boolean
Return Me.List.Contains(Value)
End Function
Public Function Item(ByVal Index As Integer) As Something
Return DirectCast(Me.List.Item(Index), Something)
End Function
Public Function Reverse() As SomeCollection
Dim revList As SomeCollection = New SomeCollection()
For index As Integer = (Me.List.Count - 1) To 0 Step -1
revList.List.Add(Me.List.Item(index))
Next
Return revList
End Function
End Class
Then you would call it like this
For Each s As Something In SomeCollection.Reverse
Next
first you should create a list(of string) in for each statement, and after that make another normal for .. step -1 ..next statement. see an example:
Dim ff As New List(Of Integer)
For Each rRow As DataRow In DeletedRows
Dim item As Integer
item = rRow.Table.Rows.IndexOf(rRow)
ff.Add(item)
Next
For i As Integer = ff.Count - 1 To 0 Step -1
dim item as integer=ff(i)
next i
i hope that be helpful
The accepted answer explains why, but to add another example, for a collection with key (SortedList, SortedDictionary, Dictionary, etc.) you can do
For Each Id as Tkey in MyCollection.Keys.Reverse
// The item in question is now available as MyCollection(Id)
Next
' create a list of whatever and step through For Loop Collecting
dim revList as New List (of ToolStripItem)
For each objItem as ToolStripItem in Menu.DropDownItems
revList.Add(objItem)
next
revList.reverse ' reverse the order of that list
' step through the reverse ordered list
for each objItem as ToolStripItem in revList
' do whatever (like removing your menu from an ArrayMenu)
next
' replace ToolStripItem and Menu.DropDownItems with whatever you need

Reflection - SetValue of array within class?

OK, I've been working on something for a while now, using reflection to accomplish a lot of what I need to do, but I've hit a bit of a stumbling block...
I'm trying to use reflection to populate the properties of an array of a child property... not sure that's clear, so it's probably best explained in code:
Parent Class:
Public Class parent
Private _child As childObject()
Public Property child As childObject()
Get
Return _child
End Get
Set(ByVal value As child())
_child = value
End Set
End Property
End Class
Child Class:
Public Class childObject
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
Private _descr As String
Public Property descr As String
Get
Return _descr
End Get
Set(ByVal value As String)
_descr = value
End Set
End Property
End Class
So, using reflection, I'm trying to set the values of the array of child objects through the parent object...
I've tried several methods... the following is pretty much what I've got at the moment (I've added sample data just to keep things simple):
Dim Results(1) As String
Results(0) = "1,2"
Results(1) = "2,3"
Dim parent As New parent
Dim child As childObject() = New childObject() {}
Dim PropInfo As PropertyInfo() = child.GetType().GetProperties()
Dim i As Integer = 0
For Each res As String In Results
Dim ResultSet As String() = res.Split(",")
ReDim child(i)
Dim j As Integer = 0
For Each PropItem As PropertyInfo In PropInfo
PropItem.SetValue(child, ResultSet(j), Nothing)
j += 1
Next
i += 1
Next
parent.child = child
This fails miserably on PropItem.SetValue with ArgumentException: Property set method not found.
Anyone have any ideas?
#Jon :-
Thanks, I think I've gotten a little further, by creating individual child objects, and then assigning them to an array... The issue is now trying to get that array assigned to the parent object (using reflection).
It shouldn't be difficult, but I think the problem comes because I don't necessarily know the parent/child types. I'm using reflection to determine which parent/child is being passed in. The parent always has only one property, which is an array of the child object. When I try assigning the child array to the parent object, I get a invalid cast exception saying it can't convert Object[] to .
EDIT:
Basically, what I have now is:
Dim PropChildInfo As PropertyInfo() = ResponseObject.GetType().GetProperties()
For Each PropItem As PropertyInfo In PropChildInfo
PropItem.SetValue(ResponseObject, ResponseChildren, Nothing)
Next
ResponseObject is an instance of the parent Class, and ResponseChildren is an array of the childObject Class.
This fails with:
Object of type 'System.Object[]' cannot be converted to type 'childObject[]'.
Firstly I'd get rid of the array part of the equation - I can't see how that's relevant. Try to write code to set the values for a single child.
Secondly, it seems that you're relying on the results of GetProperties being in the desired order - you shouldn't. There's no guarantee as to what order the properties will be returned in. You should know what order you want based on the string you're splitting, and fetch the properties by name.
Thirdly, I suspect the problem is that you've got some read-only properties as well as writeable ones. I suggest you whip up a short console app to check this out, logging what properties you're trying to set before you set it.
If this doesn't help, please post a short but complete console app which demonstrates the problem, and I'm sure we'll be able to fix it.
EDIT: Okay, if you're now stuck just on the array part, I suggest you show a short but complete example of that instead. I suspect the problem is that you've created an array of the wrong type. You can use Array.CreateInstance to create the right kind of array, which should be valid when you then set the property.
There are libraries available to make it easier (and faster) to work with reflection. For instance, Fasterflect allows you to write the following:
parent.Property("child").GetElement(index).SetFieldValue("Name",name);
This will retrieve the property called "child" on the object "parent". Since we expect it to be an array we'll grab the element at position "index" (a single child instance) and set its Name property to "name".
Disclaimer: I am involved in said project as a contributor.

How do you find the last loop in a For Each (VB.NET)?

How can I determine if I'm in the final loop of a For Each statement in VB.NET?
The generally, collections on which you can perform For Each on implement the IEnumerator interface. This interface has only two methods, MoveNext and Reset and one property, Current.
Basically, when you use a For Each on a collection, it calls the MoveNext function and reads the value returned. If the value returned is True, it means there is a valid element in the collection and element is returned via the Current property. If there are no more elements in the collection, the MoveNext function returns False and the iteration is exited.
From the above explanation, it is clear that the For Each does not track the current position in the collection and so the answer to your question is a short No.
If, however, you still desire to know if you're on the last element in your collection, you can try the following code. It checks (using LINQ) if the current item is the last item.
For Each item in Collection
If item Is Collection.Last Then
'do something with your last item'
End If
Next
It is important to know that calling Last() on a collection will enumerate the entire collection. It is therefore not recommended to call Last() on the following types of collections:
Streaming collections
Computationally expensive collections
Collections with high tendency for mutation
For such collections, it is better to get an enumerator for the collection (via the GetEnumerator() function) so you can keep track of the items yourself. Below is a sample implementation via an extension method that yields the index of the item, as well as whether the current item is the first or last item in the collection.
<Extension()>
Public Iterator Function EnumerateEx(Of T)(collection As IEnumerable(Of T))
As IEnumerable(Of (value As T, index As Integer, isFirst As Boolean, isLast As Boolean))
Using e = collection.GetEnumerator()
Dim index = -1
Dim toYield As T
If e.MoveNext() Then
index += 1
toYield = e.Current
End If
While e.MoveNext()
Yield (toYield, index, index = 0, False)
index += 1
toYield = e.Current
End While
Yield (toYield, index, index = 0, True)
End Using
End Function
Here is a sample usage:
Sub Main()
Console.WriteLine("Index Value IsFirst IsLast")
Console.WriteLine("----- ----- ------- ------")
Dim fibonacci = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89}
For Each i In fibonacci.EnumerateEx()
Console.WriteLine(String.Join(" ", $"{i.index,5}",
$"{i.value,5}",
$"{i.isFirst,-7}",
$"{i.isLast,-6}"))
Next
Console.ReadLine()
End Sub
Output
Index Value IsFirst IsLast
----- ----- ------- ------
0 0 True False
1 1 False False
2 1 False False
3 2 False False
4 3 False False
5 5 False False
6 8 False False
7 13 False False
8 21 False False
9 34 False False
10 55 False False
11 89 False True
It probably would be easier to just use a For loop instead of ForEach. But, similarly, you could keep a counter inside your ForEach loop and see if its equal to yourCollection.Count - 1, then you are in the last iteration.
With a foreach, you cannot know this until it is too late (ie. you're out of the loop).
Note, I'm assuming you're using something where you only have an IEnumerable interface. If you have a list, array, etc. then follow the other answers here which uses .Count or similar to find out how many items there are, and thus you can keep track of where you are in the collection.
However, with just IEnumerable/IEnumerator, there is no way to know for sure wether or not there are more, if you use foreach.
If you need to know this, use IEnumerable yourself, which is what foreach does.
The below solution is for C# but should translate easily to VB.NET:
List<Int32> nums = new List<Int32>();
nums.Add(1);
nums.Add(2);
nums.Add(3);
IEnumerator<Int32> enumerator = nums.GetEnumerator();
if (enumerator.MoveNext())
{
// at least one value available
while (true)
{
// make a copy of it
Int32 current = enumerator.Current;
// determine if it was the last value
// if not, enumerator.Current is now the next value
if (enumerator.MoveNext())
{
Console.Out.WriteLine("not last: " + current);
}
else
{
Console.Out.WriteLine("last: " + current);
break;
}
}
}
enumerator.Dispose();
This will print:
not last: 1
not last: 2
last: 3
The trick is to take a copy of the current value, then ask the enumerator to attempt to move on to the next one. If that fails, the copy you made was indeed the last value, otherwise there is more.
here is a simple thing i dont know if it is politically correct but it works
for each item in listOfThings
if(item = listOfThings.last)then
'you found the last loop
end if
next
Short answer: You can't
Long answer: There's nothing in the semantics of a For Each statement that allows you to identify whether you're running the first, last or any particular iteration.
For Each is built in the IEnumerable and IEnumerable<> interfaces, and the behavior is dependent on the implementation you're calling. It's valid, though confusing, for the collection you're iterating to return elements in a different order every time. Fortunately, List<> doesn't do this.
If you really need to know that a particular iteration is the last, you could identify the last element (in advance) and then take different action when you encounter that element.
Easier would be to detect the first iteration (say, through a boolean) and then do something different.
An example (C#, but the VB will be similar):
StringBuilder result = new StringBuilder();
bool firstTime = true;
foreach(string s in names)
{
if (!firstTime)
{
result.Append(", ");
}
result.Append(s);
firstTime = false;
}
Check if the element is the last element of the container.
Why do you want to do that?
You could just place instructions after the loop. (That you execute on the last element)
It would be easier to use a For loop instead of a ForEach, however you could do something like
If item.Equals(itemCollection(itemCollection.Count)) Then
...
End If
inside of your ForEach loop... Assuming the object has properly overridden the Equals method.
This is probably much more resource intensive than just using a For loop or keeping track of the current index in a separate variable.
I'm not sure if this is the correct syntax, it's been a long time since I've used VB.
Using a standard "For Each" loop you can't. The intent of a "for Each" loop is to allow you to concentrate on the data in lieu of the underlying collection.
I keep coming back to this post, so I decided to sit down and think about this issue and I came up with this:
For Each obj In myCollection
Console.WriteLine("current object: {0}", obj.ToString)
If Object.ReferenceEquals(obj, myCollection.Last()) Then
Console.WriteLine("current object is the last object")
End If
Next
You could even reduce the If...Then...End If statement to a single line if you wanted to. I'm not sure if calling .Last() on every iteration has a large impact on performance, but you can always assign it to a variable outside of the loop if that's what keeps you awake at night.
this code sample might help
For Each item in Collection
If ReferenceEquals(Collection.Item(Collection.Count - 1), item) Then
'do something with your last item'
End If
Next
If you are tied to IEnumerable. Do you have to be inside the foreach loop? If not you could declare a variable just before the foreach loop. Set it during the loop. Then, use it after the loop (if its not null)
Well there are few workaround
Suppose you are working with CheckBoxList
Then this code sample might help :
Dim count As Integer = 0
Dim totalListCount As Integer = CheckBoxList.Items.Count()
For Each li In CheckBoxList.Items
count += 1
// This is the Last Item
If count = totalListCount THEN
// DO Somthing.....
END IF
NEXT
For Each xObj In xColl
If xObj = xColl(xColl.Count) Then ...
Next