Can I force an object to be passed as "ByVal" (as a copy) in a call in .NET? - vb.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)

Related

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

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

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.

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.

VB.net class and function instance - most efficient method

I would like to know what you guys would do in this situation.
I am basically returning a data set for Person, but I would like to know the most efficient way of doing things.
Public Class TestClass
Public Shared Function returnPersonData() As Person
Dim p As New Person
p.Address = "Here and there"
p.Name = "Mike"
p.Career = "Pilot"
Return p
End Function
End Class
Person class:
Public Class Person
Public Property Name As String
Public Property Address As String
Public Property Career As String
End Class
I would then get the name by doing this in another class:
Dim name As String = TestClass.returnPersonData.Name
Dim address As String = TestClass.returnPersonData.Address
My question is this: why does it re-run the returnPersonData function every time I need to extract info the name, address and career? Why can't I just call the function once, save it in a data set, and then just reference that?
Because you are calling it twice...
Dim name As String = TestClass.returnPersonData.Name ' <--- One time here
Dim address As String = TestClass.returnPersonData.Address ' <--- An other time here
Save the person class instance
Dim currentPerson As Person = TestClass.returnPersonData
Then you can get the name or address with
Dim name As String = currentPerson.Name
Dim address As String = currentPerson.Address
You could remove those two variables and just use currentPerson all the time.

Making new object from custom class- name using a variable value?

I am making a LIST to organize and manipulate arrays that represent lines off a spreadsheet. I've created a custom class for the arrays, and will call them out as objects.
My question is, can I use a value stored in a variable as the name of the object? If so, what would the syntax look like?
dim FileName as String
FileName = 123456.csv
Public Class List_Array
public variable1 as string
public variable2 as string
public variable3 as string
public variable4 as string
End Class
dim File_Name as List_Array = NEW List_Array
This is the coding as I understand it, but I keep thinking this will only create one Object over and over again with the same name as the string variable.
If not, how can I differentiate the different objects as I call them? There will be thousands of objects to reference, so using an unnamed object will not work so well.
If you want to store a list of named objects, what you need is a Dictionary. Dictionary objects store a list of key/value pairs. In this case, the "key" is the name that you want to assign to the object, and the value is the reference to the object itself. It's a generic class, which means when you use the Dictionary type, you must specify the types that you want it to use for it's keys and values. For instance Dictionary(Of String, MyClass) will create a list that uses String objects for its keys and MyClass objects for its values.
Here's an example of how you could use a dictionary to store a list of people with their ages:
Dim d As New Dictionary(Of String, Integer)()
d("Bob") = 30
d("Mary") = 42
Then, when you want to read a value, you can do it like this:
Dim age As Integer = d("Bob")
The Dictionary will only allow one item per key. It uses a hash table to index the values by their keys, so it's very fast get any item by its key.
Edit
Based on your comments below, here's a more pertinent example to show what I mean. Let's say you have a CSV file containing a list of people. So you create a class that stores all of the information about one person (one line of the CSV file), like this:
Public Class Person
Public Property Id As String
Public Property Name As String
Public Property Title As String
End Class
Then, you create a method which parses a single line from the CSV file and returns all of the fields from that line as an array of strings, like this:
Public Function ParseCsvLine(line As String) As String()
' ...
End Function
Then, you could load all of the people into a list, like this:
Dim persons As New List(Of Person)()
For Each line As String In File.ReadAllLines("People.csv")
Dim fields() As String = ParseCsvLine(line)
Dim person As New Person()
person.Id = fields(0)
person.Name = fields(1)
person.Title = fields(2)
persons.Add(person)
Next
Now you have all of the Person objects loaded into one list. The problem is, though, that the list is not indexed. If, for instance, you needed to find the person with an ID of 100, you'd need to loop through all of the Person objects in the list until you found one with that value in it's Id property. If you want to index them by ID so that you can find them more easily/quickly, you can use a Dictionary, like this:
Dim persons As New Dictionary(Of String, Person)()
For Each line As String In File.ReadAllLines("People.csv")
Dim fields() As String = ParseCsvLine(line)
Dim person As New Person()
person.Id = fields(0)
person.Name = fields(1)
person.Title = fields(2)
persons(person.Id) = person
Next
Then, when you need to get a person from the dictionary, you can easily access it by ID, like this:
Dim person100 As Person = persons("100")