List(OF T).Contains Problem (Search through String or Integer Props of the Class) - vb.net

List(Of T).Contains function help.
Here is my simple object.
Public Class Person
Name as string
Age as Integer
End Class
In the Sub:
Dim Person as new Person
Person.Name = "John"
Person.Age = 32
Dim myPersonList as new List(of Person)
myPersonList.Add(Person)
How would I exactly look up if the List Contains a person named "John" with the myPersonList.Contains() function, Or how would i look up myPersonListContains(AGE)???. I can't neccessarily put "John" or an Age because the Contains function is asking for the Person Class, and not the string "John". I'd like to use this method instead of writing a function that iterates through every Persons "Name"

I used the Find method of List(Of T). I made your Name and Age in the Person class real Properties.
Public Class Person
Public Property Name As String
Public Property Age As Integer
End Class
Private Sub OPCode()
Dim myPersonList As New List(Of Person)
Dim p1 As New Person
p1.Name = "John"
p1.Age = 32
myPersonList.Add(p1)
Dim p2 As New Person
p2.Name = "Mathew"
p2.Age = 73
myPersonList.Add(p2)
Dim p3 As New Person
p3.Name = "Mark"
p3.Age = 90
myPersonList.Add(p3)
Dim foundInList = myPersonList.Find(Function(x) x.Name = "John")
If foundInList Is Nothing Then
MessageBox.Show("John not found")
Else
Dim JohnsAgeIs = foundInList.Age
Debug.Print(JohnsAgeIs.ToString)
'32 appears in Immediate window
End If
End Sub
Corrected per comment by #jmcilhinney. Without checking first if a person was returned by .Find a NRE could occur.

Unless your item type implements IEquatable(Of T), Contains will only check for reference equality. If you do implement IEquatable(Of Person) in the Person class, that implementation will be specific and unchanging, so you still can't compare in different ways at different times. What you should do is use the LINQ Any method instead, e.g.
If myPersonList.Any(Function(p) p.Name = "John") Then
'...
End If
If myPersonList.Any(Function(p) p.Age = 32) Then
'...
End If

Related

Object Property Being Changed When It's Not Supposed To? (Vb.net)

I'll try to keep it simple, but this is making me almost rip my hair out as I do not understand why a certain property is being changed on one of my objects when I am not assigning it a change.
Sample class:
Public Class Person
Public Name As String
Public Age as UInteger
End Class
OK cool.
In my project:
Dim Me as New Person, You as New Person
Me.Name = "John"
You.Name = "Terry"
Me = You
You.Age = 32
So I assigned the Name properties to 'Me' and 'You' respectively as John & Terry. I then coped the properties of 'You' To 'Me'.
When I go change the Age Property of 'You' to 32. The property age of 'Me' ALSO gets changed to 32 even though I never assigned it a change.
A total head scratcher as I am not quite catching the error here?
If you want to create a new object with the same property values then you have no option but to create a new object and copy the property values. There are a number of ways that you can implement the details though.
The most obvious and most basic is to simply create a new object and copy the property values:
Public Class Person
Public Property Name As String
Public Property Age As UInteger
End Class
Dim firstPerson As New Person
firstPerson.Name = "John"
firstPerson.Age = 32
Dim secondPerson As New Person
secondPerson.Name = firstPerson.Name
secondPerson.Age = firstPerson.Age
The next option to consider is to build the functionality into the type itself. If you do this, the first option is to implement the ICloneable interface and use the MemberwiseClone method:
Public Class Person
Implements ICloneable
Public Property Name As String
Public Property Age As UInteger
Public Function Clone() As Object Implements ICloneable.Clone
Return MemberwiseClone()
End Function
End Class
Dim firstPerson As New Person
firstPerson.Name = "John"
firstPerson.Age = 32
Dim secondPerson = DirectCast(firstPerson.Clone(), Person)
The ICloneable.Clone method returns an Object reference, because it must work for any type, so it's not ideal. You might choose not to implement the interface and change the return type, or you could add your own Copy method and cast the result:
Public Class Person
Implements ICloneable
Public Property Name As String
Public Property Age As UInteger
Public Function Clone() As Object Implements ICloneable.Clone
Return MemberwiseClone()
End Function
Public Function Copy() As Person
Return DirectCast(Clone(), Person)
End Function
End Class
Dim firstPerson As New Person
firstPerson.Name = "John"
firstPerson.Age = 32
Dim secondPerson = firstPerson.Copy()
That MemberwiseClone method creates a shallow copy of the current object, which means a direct copy of property value. That's fine for simple types like String and numeric types but may not be for more complex types. For instance, if you had a property that referred to a DataSet then the same property in the new object would refer to that same DataSet object, not a new DataSet containing the same data. The same goes for arrays and collections. If you don't want a shallow copy then you need to write your own code to explicitly create a deep copy in the specific way that you want, e.g.
Public Class Person
Public Property Name As String
Public Property Age As UInteger
Public ReadOnly Property Children As New List(Of Person)
Public Function Copy() As Person
Dim newPerson As New Person With {.Name = Name,
.Age = Age}
For Each child As Person In Children
newPerson.Children.Add(child.Copy())
Next
Return newPerson
End Function
End Class
Dim firstPerson As New Person
firstPerson.Name = "John"
firstPerson.Age = 32
firstPerson.Children.Add(New Person With {.Name = "Jim", .Age = 10})
firstPerson.Children.Add(New Person With {.Name = "Jane", .Age = 12})
Dim secondPerson = firstPerson.Copy()
The reason that's not done by default is that you could end up with a stack overflow. In this case, creating a copy of a Person object will also create a copy of the Person objects in its Children collection. If there is a circular reference there somewhere, i.e. two Person objects were in each other's Children collection then you would just keep creating copies until the system ran out of resources. You need to be very careful when creating deep copies in order to avoid such situations. You need to be very sure that you only go to the depth you need and that you catch any circular references and stop copying when you get back to the start of the chain.
Notice that this last example does require you to specify each property explicitly. It is possible to avoid this if you use Reflection but that is less efficient and, unless you have a ridiculous number of properties, a bit silly as the few minutes it takes to type out those properties should be a very minor annoyance.

Can I force an object to be passed as "ByVal" (as a copy) in a call in .NET?

In the VB.NET code below I want to add three persons to a list of persons. I thought that I could use the same person-object over and over again and simply change it's name before next Add-call. I was hoping that each call should copy the object and add that copy to the list... But I end up with a list of three "Kim". It seems that the list elements refers to the same object.
Class Person
Public Name As String
End Class
Sub Foo()
Dim Persons as New List(Of Person)
Dim aPerson As New Person
aPerson.Name = "John"
Persons.Add(aPerson)
aPerson.Name = "Emma"
Persons.Add(aPerson)
aPerson.Name = "Kim"
Persons.Add(aPerson)
End Sub
Can I force that aPerson is copied in each Add-call? Or do I need to create separate objects for John, Emma and Kim?
What you are doing isn't good practice as you shouldn't use a class that way.
If your names come from a source like a list you could use a For Each loop.
Dim MyNames As New List(Of String)
MyNames.Add("John")
MyNames.Add("Emma")
MyNames.Add("Kim")
For Each person_name As String In MyNames
Dim aPerson As New Person
aPerson.Name = person_name
Persons.Add(aPerson)
Next
This approach could be very practical if the data comes from a file, SQL request or JSON for example.
Otherwise you would just have:
Dim Persons as New List(Of Person)
Dim aPerson As New Person
Dim bPerson As New Person
Dim cPerson As New Person
aPerson.Name = "John"
Persons.Add(aPerson)
bPerson.Name = "Emma"
Persons.Add(bPerson)
cPerson.Name = "Kim"
Persons.Add(cPerson)

VB.Net How to update every entry in an ArrayList?

I am using a structure similar to that below. I need to loop through the 'Persons' ArrayList and set every salary to 100, whilst leaving the LastNames intact.
Structure Person
Dim LastName As String
Dim salary As Integer
End Structure
public class Test
public Shared Sub Main
Dim Persons As New ArrayList
Dim Person As New Person
With Person
.LastName = "Smith"
.salary = 50
End With
Persons.Add(Person)
With Person
.LastName = "Jones"
.salary = 20
End With
Persons.Add(Person)
With Person
.LastName = "Brown"
.salary = 80
End With
Persons.Add(Person)
End Sub
End class
I realise that a simple For Each loop won't work here. I could copy each 'Person' to a second temporary arraylist and then delete the entry in the original arraylist, but I can't figure out how to change the salary for each person and 'Add' it back again whilst keeping the 'LastName' values as they originally were.
Use a List(Of Person) instead of ArrayList (implicitly Of Object).
And just write a helper function to simplify adding. You can iterate over the List(Of Person) easily since now it's typed as Person
Structure Person
Dim LastName As String
Dim salary As Integer
End Structure
Sub Main()
Dim Persons As New List(Of Person)()
AddPerson(Persons, "Smith", 50)
AddPerson(Persons, "Jones", 20) ' poor Jonesy
AddPerson(Persons, "Brown", 80)
For Each person In Persons
person.salary = 100
Next
End Sub
Public Sub AddPerson(persons As List(Of Person), lastName As String, salary As Integer)
persons.Add(New Person() With {.LastName = lastName, .salary = salary})
End Sub
Another point
Your original code works with a For Each loop
For Each p As Person In Persons
p.salary = 100
Next
but the risk of using an ArrayList is that you can add any object to it without error. Then you may run into an issue when casting the items back to Person if you weren't disciplined to always only add a Person to it. For example
Persons.Add(New Object)
For Each p As Person In Persons
p.salary = 100
Next
would iterate until the loop encoutered the New Object at the end, then would result in a runtime error. A List(Of Person) would prevent it from ever being added in the first place, which is why it is always preferred over ArrayList for new development.
A class might work better in this situation. Also, you could set the default value of Salary to be 100 so that each object already has a default value (no need to assign it later in a loop).
Public Class Person
Dim LastName As String = ""
Dim salary As Integer = 100
Public Sub New()
'
End Sub
Public Sub New(ByVal Last_Name As String, ByVal Salary As Integer)
Me.LastName = Last_Name
Me.salary = Salary
End Sub
End Class
The suggested loop:
For Each p As Person In Persons
p.salary = 100
Next
did not work as it did not permanently write the new value to 'Persons', but after further searching I found a loop that does:
For x = 0 To Persons.Count - 1
Dim p As Person = Persons(x)
p.salary = 100
Persons(x) = p
Next
I hope this helps someone else. I have also implemented the LIST ideas - thanks.

How to define and use a collection of ClassA inside a ClassB

Say I have a class called Person, with a Name and an Age properties, and another collection called Family, with properties like Income, Address, and a collection of Persons inside it.
I cannot find a full example on how to implement this concept.
Furthermore, as I am new to both collections and classes, I am not succeeding also in making a small subroutine to use these two functions.
Here is my best try, based on the limited resources available on the internet:
' Inside the class Module Person .........................
Public pName As String
Public pAge As Integer
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(value As String)
pName = value
End Property
' Inside the class Module Family .........................
' ... Income and address Properties are supposed
' ... declared and will not be used in this trial
Private colPersons As New Collection
Function AddP(aName As String, anAge As integer)
'create a new person and add to collection
Dim P As New Person
P.Name = aName
P.Age = anAge
colPersons.Add R ' ERROR! Variable colPersons not Defined!
End Function
Property Get Count() As Long
'return the number of people
Count = colPersons.Count
End Property
Property Get Item(NameOrNumber As Variant) As Person
'return this particular person
Set Item = Person(NameOrNumber)
End Property
And now the Subroutine that tries to use the above:
Sub CreateCollectionOfPersonsInsideAFamily()
'create a new collection of people
Dim Family_A As New Family
'add 3 people to it
Family_A.AddP "Joe", 13
Family_A.AddP "Tina", 33
Family_A.AddP "Jean", 43
'list out the people
Dim i As Integer
For i = 1 To Family_A.Count
Debug.Print Family_A.Item(i).Name
Next i
End Sub
Naturally this is giving errors: Variable Not defined (see above comment)
Sorry for the inconvenience... But the problem was that the line:
Private colPersons As New Collection
should not be place after other properties have been declared (here not shown: Address and Income)
After placing this line in the declaration area at the top of its class, all the code has proved to be correct.

Merge multiple list of string to list of object using VB.NET

I have 3 list of strings.
List1 - Student Name List2 - Student School List3 - Student Location
Student 1 Student 1 School Student 1 Location
Student 2 Student 2 School Student 2 Location
Student 3 Student 3 School Student 3 Location
Student 4 Student 4 School Student 4 Location
Student 5 Student 5 School Student 5 Location
And a structure StudentDetails
Public Structure StudentDetails()
Public StudentName As String
Public StudentSchool As String
Public StudentLocation As String
End Structure
I want to make the first 3 list to List of StudentDetails
I have used the following code to do this
Dim StudentDetailsList As New List(Of StudentDetails)
For i = 0 to List1.Count - 1
Dim StudentDetail As New StudentDetail
With StudentDetail
.StudentName = List1(i)
.StudentSchool = List2(i)
.StudentLocation = List3(i)
End With
StudentDetailsList.Add(StudentDetail)
Next
Is there a better way to do this using Linq or some other method?
There are many ways to do that, some easier to read than others.
First, I would make StudentDetails a class instead of a structure (see, e.g., When should I use a struct instead of a class?.
Now that you have a class, you can give it a New constructor with parameters, as used in the third example here:
Option Infer On
Option Strict On
Module Module1
Public Class StudentDetails
Public Name As String
Public School As String
Public Location As String
Public Sub New()
' empty constuctor
End Sub
Public Sub New(name As String, school As String, location As String)
Me.Name = name
Me.School = school
Me.Location = location
End Sub
' make it easy to represent StudentDetails as a string...
Public Overrides Function ToString() As String
Return $"{Me.Name} {Me.School} {Me.Location}"
End Function
End Class
Sub Main()
Dim list1 As New List(Of String) From {"Adam", "Betty", "Charles", "Wilma"}
Dim list2 As New List(Of String) From {"Ace", "Best", "Classy", "Wacky"}
Dim list3 As New List(Of String) From {"Attic", "Basement", "Cellar", "Windowledge"}
' a not-very tidy example using Zip:
Dim StudentDetailsList = list1.Zip(list2, Function(a, b) New With {.name = a, .school = b}).Zip(list3, Function(c, d) New StudentDetails With {.Name = c.name, .School = c.school, .Location = d}).ToList()
' one way of writing out the StudentDetailsList...
For Each s In StudentDetailsList
Console.WriteLine(s.ToString())
Next
StudentDetailsList.Clear()
' a bit cleaner using a loop:
For i = 0 To list1.Count() - 1
StudentDetailsList.Add(New StudentDetails With {
.Name = list1(i),
.School = list2(i),
.Location = list3(i)})
Next
' another way of writing out the StudentDetailsList...
Console.WriteLine(String.Join(vbCrLf, StudentDetailsList))
StudentDetailsList.Clear()
' easy to write with a New constructor, but not necessarily as easy to read as the previous example:
For i = 0 To list1.Count() - 1
StudentDetailsList.Add(New StudentDetails(list1(i), list2(i), list3(i)))
Next
Console.WriteLine(String.Join(vbCrLf, StudentDetailsList))
Console.ReadLine()
End Sub
End Module
I used the $ string formatter in the .ToString() method: it was introduced with VS2015, so if you are using an earlier version you can use String.Format("{0} {1} {2}", Me.Name, Me.School, Me.Location) instead.
As a note on naming the properties of StudentDetails, the "Student" in StudentName, StudentSchool and StudentLocation is redundant.
Well, you can use an overload of Select extension method to projects each element of one of the list into a new StudentDetail by incorporating the element's index. Assuming the three lists have the same amount of elements, you can do the following:
// using C#
var result=List1.Select((e,i))=>new StudentDetail
{
StudentName =e,
StudentSchool = List2[i],
StudentLocation = List3[i]
}).ToList();
I think in Vb would be (sorry, I'm a c# programmer):
Dim StudentDetailsList=List1.Select(Function(e, i) _
New StudentDetail
With StudentDetail
.StudentName = e
.StudentSchool = List2(i)
.StudentLocation = List3(i)
End With).ToList();
But using a for is not a bad solution, in many cases is more readable.