VB.NET Class and list(of issue - vb.net

I'm using couple of custom classes and need to write data in one function and read data in another.
In order to do that, I'm using an XM file.
My issue is when I write the data.
Here is my code:
Imports System.IO
Imports System
Public Class MainForm
Public Class MyClass
Private _id As Integer
Private _somestring As String
Public Property Id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
_id = Value
End Set
End Property
Public Property Somestring() As String
Get
Return _somestring
End Get
Set(ByVal Value As String)
_somestring = Value
End Set
End Property
End Class
Private Sub mysub()
Dim ListVar As New List(Of MyClass)
' code...code.....code....
Dim newvar As New MyClass()
console.writeline("==== within the loop ====")
For i As Integer = 0 To 2
newvar.Id = I
newvar.Somestring = "hello - " & cstr(I)
ListVar.add(newvar)
console.writeline("listvar(" & i & ")=" & listvar(i).Id)
Next
console.writeline("==== outside the loop ====")
console.writeline("listvar(0)=" & listvar(0).Id)
console.writeline("listvar(1)=" & listvar(1).Id)
console.writeline("listvar(2)=" & listvar(2).Id)
End Sub
End Class
Output:
==== within the loop ====
listvar(0)=0
listvar(1)=1
listvar(2)=2
==== outside the loop ====
listvar(0)=2
listvar(1)=2
listvar(2)=2
I must be doing something stupid but I just figure out what it is....

Do this:
console.writeline("==== within the loop ====")
For i As Integer = 0 To 2
Dim newvar As New MyClass()
newvar.Id = I
newvar.Somestring = "hello - " & cstr(I)
ListVar.add(newvar)
console.writeline("listvar(" & i & ")=" & listvar(i).Id)
Next
newvar continues to be the same instance, so all three references in the List point to the same instance, and its Id value is what you set it last in your For loop.

Related

How to handle a function where the returned value type is not known at run-time (Object or Non-Object)

This question centers around the return value of a call to CallByName. I have a class called PropertyPtr which is meant to act as a generic pointer to an object property. It holds a reference to an Object, and the name of one of its properties. It exposes a Getter and Setter method.
PropertyPtr:
Option Explicit
Public Obj As Object
Public PropertyName As String
Public Sub Setter(Val As Variant)
If IsObject(Val) Then
CallByName Me.Obj, Me.PropertyName, VbSet, Val
Else
CallByName Me.Obj, Me.PropertyName, VbLet, Val
End If
End Sub
Public Function Getter() As Variant
If IsObject(CallByName(Me.Obj, Me.PropertyName, VbGet)) Then
Set Getter = CallByName(Me.Obj, Me.PropertyName, VbGet)
Else
Getter = CallByName(Me.Obj, Me.PropertyName, VbGet)
End If
End Function
In the Getter, my CallByName could return a object or not. But the only way I can see to test if the CallByName value will be an object is to end up running it twice - once to test inside an IsObject and then again to get a reference to the value. The only other way I could see doing this is trapping for an error. Then, you at least only SOMETIMES run the CallByName twice.
My question is: is there some other way to do this without running CallByName twice?
Okay, so if you really want to follow that route then I think you'll have to set an IsObj flag - probably at the point you set the property name.
However, I'd still maintain that using a Variant for either an Object or primitive type isn't a great idea, and the CallByName() function in this context comes with issues. My hesitations are that performance will be diminished and you'll have quite a task to keep the property strings aligned with the property names (should you update things in the future).
It is possible to implement a Mediator Pattern in VBA and I do feel you should consider this route. Below is a really basic example of how you could do it. I haven't bothered with an interface for the mediator, but I have created an interface for my participating classes (to cover the possibility that you're dealing with your own 'groups' of classes).
Mediator class (called cMediator):
Option Explicit
Private mSweets As Collection
Private Sub Class_Initialize()
Set mSweets = New Collection
End Sub
Public Sub RegisterSweet(sweet As ISweet)
Set sweet.Mediator = Me
mSweets.Add sweet
End Sub
Public Sub SendSugarLimit(limit As Long)
Dim sweet As ISweet
For Each sweet In mSweets
sweet.ReceiveSugarLimit limit
Next
End Sub
Public Sub ReceiveMeltingAlert(offender As String)
Dim sweet As ISweet
For Each sweet In mSweets
sweet.ReceiveEatNow offender
Next
End Sub
Participating classes Interface (called ISweet):
Option Explicit
Public Property Set Mediator(RHS As cMediator)
End Property
Public Sub ReceiveSugarLimit(g_perDay As Long)
End Sub
Public Sub ReceiveEatNow(offender As String)
End Sub
My two participating classes (cQtySweet and cWeightSweet):
Option Explicit
Implements ISweet
Public Name As String
Public SugarPerItem As Long
Public CanMelt As Boolean
Private pMediator As cMediator
Public Sub OhNoItsMelting()
pMediator.ReceiveMeltingAlert Name
End Sub
Private Property Set ISweet_Mediator(RHS As cMediator)
Set pMediator = RHS
End Property
Private Sub ISweet_ReceiveEatNow(offender As String)
If CanMelt Then Debug.Print offender & " is melting. Eat " & Name & "s now!"
End Sub
Private Sub ISweet_ReceiveSugarLimit(g_perDay As Long)
Dim max As Long
max = g_perDay / SugarPerItem
Debug.Print "Max " & Name & "s: " & max & "."
End Sub
Option Explicit
Implements ISweet
Public Name As String
Public SugarPer100g As Long
Public CanMelt As Boolean
Private pMediator As cMediator
Public Sub OhNoItsMelting()
pMediator.ReceiveMeltingAlert Name
End Sub
Private Property Set ISweet_Mediator(RHS As cMediator)
Set pMediator = RHS
End Property
Private Sub ISweet_ReceiveEatNow(offender As String)
If CanMelt Then Debug.Print offender & " is melting. Eat " & Name & " now!"
End Sub
Private Sub ISweet_ReceiveSugarLimit(g_perDay As Long)
Dim max As Long
max = g_perDay / (SugarPer100g / 100)
Debug.Print "Max " & Name & ": " & max & "g."
End Sub
Module Code:
Public Sub RunMe()
Dim m As cMediator
Dim qtySweet As cQtySweet
Dim weightSweet As cWeightSweet
Set m = New cMediator
Set qtySweet = New cQtySweet
With qtySweet
.Name = "Gobstopper"
.SugarPerItem = 5
.CanMelt = False
End With
m.RegisterSweet qtySweet
Set qtySweet = New cQtySweet
With qtySweet
.Name = "Wine Gum"
.SugarPerItem = 2
.CanMelt = True
End With
m.RegisterSweet qtySweet
Set weightSweet = New cWeightSweet
With weightSweet
.Name = "Sherbert"
.SugarPer100g = 80
.CanMelt = False
End With
m.RegisterSweet weightSweet
Set weightSweet = New cWeightSweet
With weightSweet
.Name = "Fudge"
.SugarPer100g = 50
.CanMelt = True
End With
m.RegisterSweet weightSweet
'Blasted government has reduced sugar allowance.
Debug.Print "New govt. limits..."
m.SendSugarLimit 200
'Phew what a scorcher - the fudge is melting in my pocket.
Debug.Print "Sweet alarm..."
weightSweet.OhNoItsMelting
End Sub
… and the output looks like this:
New govt. limits...
Max Gobstoppers: 40.
Max Wine Gums: 100.
Max Sherbert: 250g.
Max Fudge: 400g.
Sweet alarm...
Fudge is melting. Eat Wine Gums now!
Fudge is melting. Eat Fudge now!

Parse custom language syntax

I am developing a server-side scripting language which I intend to use on my private server. It is similar to PHP, and I know that I could easily use PHP instead but I'm just doing some programming for fun.
The syntax of basic commands in my language is as follows:
command_name "parameter1" : "parameter2" : "parameter3"
But it can also be like this when I want to join values for a parameter:
command_name "parameter1" : "param" & "eter2" : "par" & "amet" & "er3"
How would I go about parsing a string like the ones shown above (it will be perfectly typed, no syntax errors) to an object that has these properties
Custom class "Request"
Property "Command" as String, should be the "command_name" part
Property "Parameters" as String(), should be an array of Parameter objects
Shared Function FromString(s As String) as Request, this should accept a string in the language above and parse it to a Request object
Custom class "Parameter"
Property "Segments" as String(), for example "para", "mete", and "r3"
Sub New(ParamArray s as String()), this is how it should be generated from the code
It should be done in VB.NET and I am a moderate level programmer, so even if you just have an idea of how to attack this then please share it with me. I am very new to parsing complex data like this so I need a lot of help. Thanks so much!
Here is another method that is simpler.
Module Module1
Sub Main()
Dim inputs As String() = {"command_name ""parameter1"" : ""parameter2"" : ""parameter3""", "command_name ""parameter1"" : ""param"" & ""eter2"" : ""par"" & ""amet"" & ""er3"""}
For Each _input As String In inputs
Dim commandStr As String = _input.Substring(0, _input.IndexOf(" ")).Trim()
Dim parameters As String = _input.Substring(_input.IndexOf(" ")).Trim()
Dim parametersA As String() = parameters.Split(":".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).Select(Function(x) x.Trim()).ToArray()
Dim parametersB As String()() = parametersA.Select(Function(x) x.Split("&".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).Select(Function(y) y.Trim(" """.ToCharArray())).ToArray()).ToArray()
Dim newCommand As New Command() With {.name = commandStr, .parameters = parametersB.Select(Function(x) New Parameter(x)).ToArray()}
Command.commands.Add(newCommand)
Next (_input)
Dim z = Command.commands
End Sub
End Module
Public Class Command
Public Shared commands As New List(Of Command)
Public name As String
Public parameters As Parameter()
End Class
Public Class Parameter
Sub New()
End Sub
Sub New(names As String())
Me.names = names
End Sub
Public names As String()
End Class
I figured it out myself
Module Module1
Sub Main()
Dim r As Request = Request.Parse(Console.ReadLine())
Console.WriteLine("The type of request is " & r.Name)
For Each p As Parameter In r.Parameters
Console.WriteLine("All segments inside of parameter " & r.Parameters.IndexOf(p).ToString)
For Each s As String In p.Segments
Console.WriteLine(" Segment " & p.Segments.IndexOf(s).ToString & " is " & s)
Next
Next
Main()
End Sub
Public Class Request
Public Name As String
Public Parameters As New List(Of Parameter)
Public Shared Function Parse(line As String)
Dim r As New Request
r.Name = line.Split(" ")(0)
Dim u As String = line.Substring(line.IndexOf(" "), line.Length - line.IndexOf(" "))
Dim p As String() = u.Split(":")
For Each n As String In p
Dim b As String() = n.Split("&")
Dim e As New List(Of String)
For Each m As String In b
Dim i As Integer = 0
Do Until i > m.Length - 1
If m(i) = ControlChars.Quote Then
Dim s As String = ""
i += 1
Do Until i > m.Length - 1 Or m(i) = ControlChars.Quote
s &= m(i)
i += 1
Loop
e.Add(s)
End If
i += 1
Loop
Next
r.Parameters.Add(New Parameter(e.ToArray))
Next
Return r
End Function
End Class
Public Class Parameter
Public Segments As New List(Of String)
Public Sub New(ParamArray s As String())
Segments = s.ToList
End Sub
End Class
End Module

Delete duplicates from list

I have the following class :
Public Class titlesclass
Public Property Link As String
Public Property Title As String
Public Function Clear()
Link.Distinct().ToArray()
Title.Distinct().ToArray()
End Function
End Class
And the following code :
For Each title As Match In (New Regex(pattern).Matches(content)) 'Since you are only pulling a few strings, I thought a regex would be better.
Dim letitre As New titlesclass
letitre.Link = title.Groups("Data").Value
letitre.Title = title.Groups("Dataa").Value
lestitres.Add(letitre)
'tempTitles2.Add(title.Groups("Dataa").Value)
Next
I tried to delete the duplicated strings using the simple way
Dim titles2 = lestitres.Distinct().ToArray()
And calling the class function :
lestitres.Clear()
But the both propositions didn't work , i know that i'm missing something very simple but still can't find what it is
Easier to use a class that already implements IComparable:
Dim query = From title In Regex.Matches(content, pattern).Cast(Of Match)
Select Tuple.Create(title.Groups("Data").Value, title.Groups("Dataa").Value)
For Each letitre In query.Distinct
Debug.Print(letitre.Item1 & ", " & letitre.Item2)
Next
or Anonymous Types:
Dim query = From title In Regex.Matches(content, pattern).Cast(Of Match)
Select New With {Key .Link = title.Groups("Data").Value,
Key .Title = title.Groups("Dataa").Value}
For Each letitre In query.Distinct
Debug.Print(letitre.Link & ", " & letitre.Title)
Next
Ok, Since I notice you are using a ClassHere is one option you can do in order to not add duplicate items to your List within a class.I'm using a console Application to write this example, it shouldn't be too hard to understand and convert to a Windows Form Application if need be.
Module Module1
Sub Main()
Dim titlesClass = New Titles_Class()
titlesClass.addNewTitle("myTitle") ''adds successfully
titlesClass.addNewTitle("myTitle") '' doesn't add
End Sub
Public Class Titles_Class
Private Property Title() As String
Private Property TitleArray() As List(Of String)
Public Sub New()
TitleArray = New List(Of String)()
End Sub
Public Sub addNewTitle(title As String)
Dim added = False
If Not taken(title) Then
Me.TitleArray.Add(title)
added = True
End If
Console.WriteLine(String.Format("{0}", If(added, $"{title} has been added", $"{title} already exists")))
End Sub
Private Function taken(item As String) As Boolean
Dim foundItem As Boolean = False
If Not String.IsNullOrEmpty(item) Then
foundItem = Me.TitleArray.Any(Function(c) -1 < c.IndexOf(item))
End If
Return foundItem
End Function
End Class
End Module
Another option would be to use a HashSet, It will never add a duplicate item, so even if you add an item with the same value, it wont add it and wont throw an error
Sub Main()
Dim titlesClass = New HashSet(Of String)
titlesClass.Add("myTitle") ''adds successfully
titlesClass.Add("myTitle") '' doesn't add
For Each title As String In titlesClass
Console.WriteLine(title)
Next
End Sub
With all of that aside, have you thought about using a Dictionary so that you could have the title as the key and the link as the value, that would be another way you could not have a list (dictionary) contain duplicate items

Putting Excel Tables into a Custom Class

I'm new to using VBA classes, and I think what I want to do -- using them with ListObjects -- is more of an "intermediate" than "beginner" technique.
Let's say I have two tables related to cars.
tblCarDesc
ID MAKE MODEL DOORS ENGINE
1 Chevrolet Corvette 2 V8
2 Ford Escort 4 V6
3 Rolls-Royce SilverCloud 4 V8
tblCarProd
ID COUNTRY TYPE
1 US Sport
2 US Economy
3 UK Luxury
(The same cars are in both tables, and shown by the ID numbers.)
I want to have a class called objCars that includes the fields (columns) from both tables. That way, when referring to Car #3, objCars.Make would be "Rolls-Royce" and objCars.Type would be "Luxury".
1) Is there a way to import both tables into objCars?
Perhaps I would create an array big enough to hold all the columns, then load both tables into it. The tutorial I've been reading says that I would then create a Collection, loop through each row of the array, make a new instance of objCars, and assign objCars.Make, objCars.Model, etc., for each row. Then each item of the Collection would contain a car. (Or something like that. I don't really know much about Collections either.) If that's right, is it the best way?
2) How exactly does one refer to a specific car? The examples I've read like to loop through Collections and work on each item therein, but what if I want to extract a particular item? I know the Ford is Car #2; how do I get the objCars.Make and objCars.Model for that particular ID number?
I would have two classes. A class clsCar for one car and a class clsCars for a collection of cars.
Each of this classes may have setter and getter methods and also may have custom methods if needed. Especially the clsCars should have a set of getBy...-methods to get a car or a collection of cars from the collection by criterion.
Example:
clsCar:
Private pID As Long
Private pMAKE As String
Private pMODEL As String
Private pDOORS As Integer
Private pENGINE As String
Private pCOUNTRY As String
Private pTYPE As String
Public Property Get ID() As Long
ID = pID
End Property
Public Property Let ID(Value As Long)
pID = Value
End Property
Public Property Get MAKE() As String
MAKE = pMAKE
End Property
Public Property Let MAKE(Value As String)
pMAKE = Value
End Property
Public Property Get MODEL() As String
MODEL = pMODEL
End Property
Public Property Let MODEL(Value As String)
pMODEL = Value
End Property
Public Property Get DOORS() As Integer
DOORS = pDOORS
End Property
Public Property Let DOORS(Value As Integer)
pDOORS = Value
End Property
Public Property Get ENGINE() As String
ENGINE = pENGINE
End Property
Public Property Let ENGINE(Value As String)
pENGINE = Value
End Property
Public Property Get COUNTRY() As String
COUNTRY = pCOUNTRY
End Property
Public Property Let COUNTRY(Value As String)
pCOUNTRY = Value
End Property
Public Property Get CarTYPE() As String
CarTYPE = pTYPE
End Property
Public Property Let CarTYPE(Value As String)
pTYPE = Value
End Property
Public Function toString() As String
toString = pID & "; " & _
pMAKE & "; " & _
pMODEL & "; " & _
pDOORS & "; " & _
pENGINE & "; " & _
pCOUNTRY & "; " & _
pTYPE
End Function
clsCars:
Private pCars As collection
Private Sub Class_Initialize()
Set pCars = New collection
End Sub
Public Sub add(oCar As clsCar)
pCars.add oCar
End Sub
Public Function getByIndex(lIndex As Long) As clsCar
Set getByIndex = pCars.Item(lIndex)
End Function
Public Function getByID(lID As Long) As clsCar
Dim oCar As clsCar
For Each oCar In pCars
If oCar.ID = lID Then
Set getByID = oCar
End If
Next
End Function
Public Function getByEngine(sEngine As String) As collection
Dim oCar As clsCar
Set getByEngine = New collection
For Each oCar In pCars
If oCar.ENGINE = sEngine Then
getByEngine.add oCar
End If
Next
End Function
default Module:
Public oCars As clsCars
Sub initialize()
Dim oCar As clsCar
Dim oListObject As ListObject
Dim oListRow As ListRow
Dim oCells As Range
Set oCars = New clsCars
Set oListObject = Worksheets("Sheet1").ListObjects("tblCarDesc")
For Each oListRow In oListObject.ListRows
Set oCells = oListRow.Range.Cells
Set oCar = New clsCar
oCar.ID = oCells(, 1).Value
oCar.MAKE = oCells(, 2).Value
oCar.MODEL = oCells(, 3).Value
oCar.DOORS = oCells(, 4).Value
oCar.ENGINE = oCells(, 5).Value
oCars.add oCar
Next
Set oListObject = Worksheets("Sheet1").ListObjects("tblCarProd")
Dim lID As Long
For Each oListRow In oListObject.ListRows
Set oCells = oListRow.Range.Cells
lID = oCells(, 1).Value
Set oCar = oCars.getByID(lID)
If Not oCar Is Nothing Then
oCar.COUNTRY = oCells(, 2).Value
oCar.CarTYPE = oCells(, 3).Value
End If
Next
MsgBox oCars.getByIndex(2).toString
For Each oCar In oCars.getByEngine("V8")
MsgBox oCar.toString
Next
End Sub
I would use a class for each, and an array of each also, so arrCars holds clsCars, and arrProd holds clsProduction. I would then use the index of each for each array when populating, so arrCars(1)=Corvette and arrProd(1)=US Sport then from each you can refer to the others, so if x=3, cars(x) and prod(x) will be correct. Or use a vlookup in excel first, and make one larger table, with the need for only 1 ID then, if that is the way they are related, but can a Bentley also be 3. Its not quite clear what you mean by the 2nd table, is there an entry for each car, or is it like catgeorising the car further, just using a certain selection. Another idea would be to have an extra property in the car class, of ProductionID and then use a static production "table" of classes to refer to.

Should I use two methods with String and Integer,or use TypeOf?

We have a series of collections of objects that all have two fields for sure, an integer "key" and a string "name". We have methods that return a particular instance based on the name or key...
Public ReadOnly Property Inflations(ByVal K as String) As InflationRow
' look for K in the names
End Property
Public ReadOnly Property Inflations(ByVal K as Integer) As InflationRow
' look for K in the keys
End Property
COM interop has the interesting side effect that only the first method with a given name is exported. So we added this...
Public ReadOnly Property Inflations(ByVal K as Object) As InflationRow
Return Inflations(K)
End Property
This leads to some confusion when reading the code, and multiple lines doing the same thing. So what if I replace all of this with...
Public ReadOnly Property Inflations(ByVal K as Object) As InflationRow
If TypeOf K Is String then
'do a string lookup on name
else
'try it on the key
end if
End Property
This does the same thing in the end, but seems much easier to read and keeps all the code in the same place. But...
Most of the calls into this code doesn't come from COM, but our own code. Will many calls to TypeOf in our .net code be significantly slower than allowing the runtime to make this decision through polymorphism? I really don't know enough about the runtime to even guess.
Test it and see! :-)
Option Strict On
Module Module1
Sub Main()
Dim irc As New InflationRowCollection
For i As Integer = 0 To 4999
irc.InflationList.Add(New InflationRow With {.IntProperty = i, .StrProperty = i.ToString})
Next i
Dim t1 As Date = Now
For i As Integer = 0 To 4999
Dim ir1 As InflationRow = irc.Inflations(i)
Dim ir2 As InflationRow = irc.Inflations(i.ToString)
Next i
Dim t2 As Date = Now
For i As Integer = 0 To 4999
Dim ir1 As InflationRow = irc.InflationsObj(i)
Dim ir2 As InflationRow = irc.InflationsObj(i.ToString)
Next i
Dim t3 As Date = Now
Console.WriteLine("Typed property: " & (t2 - t1).TotalSeconds & " sec" & vbCrLf & "Object property: " & (t3 - t2).TotalSeconds & " sec")
Console.ReadKey()
End Sub
End Module
Class InflationRow
Property IntProperty As Integer
Property StrProperty As String
End Class
Class InflationRowCollection
Property InflationList As New List(Of InflationRow)
ReadOnly Property InflationsObj(o As Object) As InflationRow 'use different name for testing, so we can compare
Get
If TypeOf o Is String Then
Return Inflations(DirectCast(o, String))
ElseIf TypeOf o Is Integer Then
Return Inflations(DirectCast(o, Integer))
Else
Throw New ArgumentException
End If
End Get
End Property
ReadOnly Property Inflations(k As String) As InflationRow
Get
For Each ir As InflationRow In InflationList
If ir.StrProperty = k Then Return ir
Next
Return Nothing
End Get
End Property
ReadOnly Property Inflations(k As Integer) As InflationRow
Get
For Each ir As InflationRow In InflationList
If ir.IntProperty = k Then Return ir
Next
Return Nothing
End Get
End Property
End Class