Parsing latin plant names with piglet fluent configuration - vb.net

I have the following tests.And Classes. Now I just need to find how to write the rules and it seemed so simple ;-). But I'm going nowhere fast. As the tags say, I would like to use and learn piglet for this.
Public Class Plant
Public Property Genus As String
Public Property Species As String
Public Property SubSpecies As String
Public Property IsHybrid As Boolean
End Class
Public Class ParserTests
<Test>
Public Sub IfGenusCanBeFoundWhenOnlyGenusAndSpiecesAreThere()
Dim parser = New ParseLatinPlantName
Dim result = parser.Parse("Salvia sylvatica")
Assert.AreEqual("Salvia", result.Genus)
End Sub
<Test>
Public Sub IfSpeciesCanBeFoundWhenOnlyGenusAndSpiecesAreThere()
Dim parser = New ParseLatinPlantName
Dim result = parser.Parse("Salvia sylvatica")
Assert.AreEqual("sylvatica", result.Species)
End Sub
<Test>
Public Sub IfSubSpeciesCanBeFoundWhenSubSpeciesIsProvided()
Dim parser = New ParseLatinPlantName
Dim result = parser.Parse("Salvia sylvatica sp. crimsonii")
Assert.AreEqual("crimsonii", result.SubSpecies)
End Sub
<Test>
Public Sub IfIsHybridIsTrueWhenxIsInNameCanBeFoundWhenSubSpeciesIsProvided()
Dim parser = New ParseLatinPlantName
Dim result = parser.Parse("Salvia x jamensis")
Assert.IsTrue(result.IsHybrid)
End Sub
End Class
And here is what I tried so far.
Public Class ParseLatinPlantName
Public Function Parse(ByVal name As String) As Plant
Dim config = ParserFactory.Fluent()
Dim expr = config.Rule()
Dim name1 = config.Expression()
name1.ThatMatches("[a-z]+").AndReturns(Function(f) f)
Dim space1 = config.Expression()
space1.ThatMatches(" ").AndReturns(Function(f) f)
expr.IsMadeUp.By(name).As("Genus").Followed.By(name).As("Species").WhenFound(Function(f) New Plant With {.Genus = f.Genus})
Dim parser = config.CreateParser()
Dim result = DirectCast(parser.Parse(name), Plant)
Return result
End Function
End Class
Update
I got the first two tests passing thanks to Randompunter.
Public Class ParseLatinPlantName
Public Function Parse(ByVal name As String) As Plant
Dim config = ParserFactory.Fluent()
Dim expr = config.Rule()
Dim name1 = config.Expression()
name1.ThatMatches("\w+").AndReturns(Function(f) f)
expr.IsMadeUp.By(name1).As("Genus") _
.Followed.By(name1).As("Species") _
.WhenFound(Function(f) New Plant With {.Genus = f.Genus, .Species = f.Species})
Dim parser = config.CreateParser()
Dim result = DirectCast(parser.Parse(name), Plant)
Return result
End Function
End Class

Firstly, your original (then corrected expression matched only lowercase letters). This was corrected by changing it to \w+ which matched any other letter.
You second two tests failed because your grammar does not allow for more than two following letters. You will need to add a rule to make this work.
For instance, you have an example where a subspecies is provided. Assume that this takes the form where .sp xxx is an optional thing to pass, a separate rule needs to provided for this.
This passes the test for an optional subspecies
Public Class ParseLatinPlantName
Public Function Parse(ByVal name As String) As Plant
Dim config = ParserFactory.Fluent()
Dim expr = config.Rule()
Dim subSpecies = config.Rule()
Dim sp = config.Expression()
sp.ThatMatches("sp\.").AndReturns(Function(f) f)
Dim name1 = config.Expression()
name1.ThatMatches("\w+").AndReturns(Function(f) f)
Dim nothing1 = config.Rule()
expr.IsMadeUp.By(name1).As("Genus") _
.Followed.By(name1).As("Species") _
.Followed.By(subSpecies).As("Subspecies") _
.WhenFound(Function(f) New Plant With {.Genus = f.Genus, .Species = f.Species, .SubSpecies = f.Subspecies})
subSpecies.IsMadeUp.By(sp).Followed.By(name1).As("Subspecies").WhenFound(Function(f) f.Subspecies) _
.Or.By(nothing1)
Dim parser = config.CreateParser()
Dim result = DirectCast(parser.Parse(name), Plant)
Return result
End Function
End Class
Excuse my probably extremely shoddy VB, it was ages ago. Note that there is an expression that explicitly matches "sp." to distinguish it from any other type of name. This rule is then also matched by another rule that matches nothing. This enables the subspecies part to be optional.
I'm not to sure what you want parsed from the hybrid rule. I assume it must be something with name followed by an x and followed by some other name then it is a hybrid. To match this, add another rule to your parser.
The following parser passes all of your tests:
Public Class ParseLatinPlantName
Public Function Parse(ByVal name As String) As Plant
Dim config = ParserFactory.Fluent()
Dim expr = config.Rule()
Dim subSpecies = config.Rule()
Dim hybridIndicator = config.Expression
hybridIndicator.ThatMatches("x").AndReturns(Function(f) f)
Dim sp = config.Expression()
sp.ThatMatches("sp\.").AndReturns(Function(f) f)
Dim name1 = config.Expression()
name1.ThatMatches("\w+").AndReturns(Function(f) f)
Dim nothing1 = config.Rule()
expr.IsMadeUp.By(name1).As("Genus") _
.Followed.By(name1).As("Species") _
.Followed.By(subSpecies).As("Subspecies") _
.WhenFound(Function(f) New Plant With {.Genus = f.Genus, .Species = f.Species, .SubSpecies = f.Subspecies}) _
.Or.By(name1).As("FirstSpecies").Followed.By(hybridIndicator).Followed.By(name1).As("SecondSpecies") _
.WhenFound(Function(f) New Plant With {.IsHybrid = True})
subSpecies.IsMadeUp.By(sp).Followed.By(name1).As("Subspecies").WhenFound(Function(f) f.Subspecies) _
.Or.By(nothing1)
Dim parser = config.CreateParser()
Dim result = DirectCast(parser.Parse(name), Plant)
Return result
End Function
End Class
It is important that your expressions if they overlap are declared in the order of precedence. If you were to declare name1 before hybridIndicator the x would be recognized as a name, causing the parsing to fail. And as you probably noticed, Piglet ignores whitespace by default, there is no need to make a rule for it. If this setting is not desired, there is an option to turn it off in the configurator. (use the Ignore method)

Related

Split a string array problems vb.net

I am new to VB.NET and would like to split a string into an array.
I have a string like:
613,710,200,127,127,'{\"js\":{\"\":\"16\",\"43451\":\"16\",\"65815\":\"16\",\"43452\":\"16\",\"41147\":\"16\",\"43449\":\"16\",\"43467\":\"16\",\"1249\":\"16\",\"43462\":\"16\",\"43468\":\"48\",\"43438\":\"64\",\"43439\":\"80\"}}','rca',95,2048000,3,1,'AABBCCDDEEFFGGHHIIJJKKLL=','xx.xx.xx.xx',NULL
I want to split this into a array at ",".
I tried:
Dim variable() As String
Dim stext As String
stext = "mystringhere"
variable = Split(stext, ",")
My problem is the part of
'{\"js\":{\"\":\"16\",\"43451\":\"16\",\"65815\":\"16\",\"43452\":\"16\",\"41147\":\"16\",\"43449\":\"16\",\"43467\":\"16\",\"1249\":\"16\",\"43462\":\"16\",\"43468\":\"48\",\"43438\":\"64\",\"43439\":\"80\"}}',
is split too. I want this to get all together in variable(5). Is this posible?
thank you for help
What you need is a CSV parser in which you can set the field quote character. Unfortunately the TexFieldParser which comes with VB.NET doesn't have that facility. Fortunately, other ones do - here I have used the LumenWorksCsvReader, which is available as a NuGet package *.
Option Strict On
Option Infer On
Imports System.IO
Imports LumenWorks.Framework.IO.Csv
Module Module1
Sub Main()
Dim s = "613,710,200,127,127,'{\""js\"":{\""\"":\""16\"",\""43451\"":\""16\"",\""65815\"":\""16\"",\""43452\"":\""16\"",\""41147\"":\""16\"",\""43449\"":\""16\"",\""43467\"":\""16\"",\""1249\"":\""16\"",\""43462\"":\""16\"",\""43468\"":\""48\"",\""43438\"":\""64\"",\""43439\"":\""80\""}}','rca',95,2048000,3,1,'AABBCCDDEEFFGGHHIIJJKKLL=','xx.xx.xx.xx',NULL"
Using sr As New StringReader(s)
Using csvReader = New CsvReader(sr, delimiter:=","c, quote:="'"c, escape:="\"c, hasHeaders:=False)
Dim nFields = csvReader.FieldCount
While csvReader.ReadNextRecord()
For i = 0 To nFields - 1
Console.WriteLine(csvReader(i))
Next
End While
End Using
End Using
Console.ReadLine()
End Sub
End Module
which outputs
613
710
200
127
127
{"js":{"":"16","43451":"16","65815":"16","43452":"16","41147":"16","43449":"16","43467":"16","1249":"16","43462":"16","43468":"48","43438":"64","43439":"80"}}
rca
95
2048000
3
1
AABBCCDDEEFFGGHHIIJJKKLL=
xx.xx.xx.xx
NULL
Note that the double-quotes are doubled up in the literal string as that is the way to enter a single double-quote in VB.
If you really want the backslashes to remain, remove the escape:="\"c parameter.
If you are reading from a file then use the appropriate StreamReader instead of the StringReader.
Using the above, perhaps you have a Windows Forms program where you wanted to populate a RichTextBox with the data from, say, a text file named "C:\temp\CsvFile.txt" with the content
613,710,200,127,127,'{\""js\"":{\""\"":\""16\"",\""43451\"":\""16\"",\""65815\"":\""16\"",\""43452\"":\""16\"",\""41147\"":\""16\"",\""43449\"":\""16\"",\""43467\"":\""16\"",\""1249\"":\""16\"",\""43462\"":\""16\"",\""43468\"":\""48\"",\""43438\"":\""64\"",\""43439\"":\""80\""}}','rca',95,2048000,3,1,'AABBCCDDEEFFGGHHIIJJKKLL=','xx.xx.xx.xx',NULL
614,710,200,127,127,'{\""js\"":{\""\"":\""16\"",\""43451\"":\""16\"",\""65815\"":\""16\"",\""43452\"":\""16\"",\""41147\"":\""16\"",\""43449\"":\""16\"",\""43467\"":\""16\"",\""1249\"":\""16\"",\""43462\"":\""16\"",\""43468\"":\""48\"",\""43438\"":\""64\"",\""43439\"":\""80\""}}','din',95,2048000,3,1,'AABBCCDDEEFFGGHHIIJJKKLL=','yy.yy.yy.yy',NULL
615,710,200,127,127,'{\""js\"":{\""\"":\""16\"",\""43451\"":\""16\"",\""65815\"":\""16\"",\""43452\"":\""16\"",\""41147\"":\""16\"",\""43449\"":\""16\"",\""43467\"":\""16\"",\""1249\"":\""16\"",\""43462\"":\""16\"",\""43468\"":\""48\"",\""43438\"":\""64\"",\""43439\"":\""80\""}}','jst',95,2048000,3,1,'AABBCCDDEEFFGGHHIIJJKKLL=','zz.zz.zz.zz',NULL
you could use the above to come up with
Imports System.IO
Imports LumenWorks.Framework.IO.Csv
Public Class Form1
Public Class Datum
Property A As Integer
Property B As Integer
Property C As Integer
Property D As Integer
Property E As Integer
Property JsonData As String
Property SocketType As String
Property F As Integer
Property G As Integer
Property H As Integer
Property I As Integer
Property Base64Data As String
Property IpAddy As String
Property J As String
Public Overrides Function ToString() As String
Return $"{A}, {SocketType}, {IpAddy}, {B} ,{C}, {D}, {E}, {F}, {G}, {H}, {I}, {JsonData}, {Base64Data}, {J}"
End Function
End Class
Public Function GetData(filename As String) As List(Of Datum)
Dim data As New List(Of Datum)
Using sr As New StreamReader(filename)
Using csvReader = New CsvReader(sr, hasHeaders:=False, delimiter:=","c, quote:="'"c, escape:="\"c, comment:=Nothing, trimmingOptions:=ValueTrimmingOptions.UnquotedOnly)
Dim nFields = csvReader.FieldCount
If nFields <> 14 Then
Throw New MalformedCsvException("Did not find 14 fields in the file " & filename)
End If
While csvReader.ReadNextRecord()
Dim d As New Datum()
d.A = Integer.Parse(csvReader(0))
d.B = Integer.Parse(csvReader(1))
d.C = Integer.Parse(csvReader(2))
d.D = Integer.Parse(csvReader(3))
d.E = Integer.Parse(csvReader(4))
d.JsonData = csvReader(5)
d.SocketType = csvReader(6)
d.F = Integer.Parse(csvReader(7))
d.G = Integer.Parse(csvReader(8))
d.H = Integer.Parse(csvReader(9))
d.I = Integer.Parse(csvReader(10))
d.Base64Data = csvReader(11)
d.IpAddy = csvReader(12)
d.J = csvReader(13)
data.Add(d)
End While
End Using
End Using
Return data
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim srcFile = "C:\temp\CsvData.txt"
Dim dat = GetData(srcFile)
For Each d In dat
RichTextBox1.AppendText(d.ToString() & vbCrLf)
Next
End Sub
End Class
It might be necessary to perform more checks on the data when trying to parse it. Note that I made a function for the .ToString() method of the Datum class and put the properties in a different order just to demonstrate its use.
* Tools -> NuGet Package Manager -> Manage NuGet Packages for Solution... Choose the "Browse" tab -> type in LumenWorksCsvReader -> select the one by Sébastien Lorion et al., -> tick your project name in the pane to the right -> click Install.
I am new to VB.NET and would like to split a string into an array.
...
variable = Split(stext,",")
Instead of
variable = Split(stext,",")
use
variable = stext.split(",")
If you want to get a bit more complicated on your split you would create an array of char data as such
dim data(3) as char
data(0) = ","c
data(1) = vbcrlf
data(2) = chr(34)
data(3) = vbtab
... and so on
variable = stext.split(data)

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

Is there a way to dynamically specify property names in a class?

VB.NET 2010~Framework 3.5
Is there a way to dynamically specify property names of a class?
Sometimes I need a list created from Prop1 and Prop2
Other times I need a list created from Prop2 and Prop4 etc.. The target properties are not known ahead of time, they constantly change as the app is running. . .
Option Strict On
Option Explicit On
Public Class Form1
Private Class Things
Public Property Prop1 As String
Public Property Prop2 As String
Public Property Prop3 As String
Public Property Prop4 As String
End Class
Private Class SubThing
Public Property P1 As String
Public Property P2 As String
End Class
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim mainLst As New List(Of Things)
Dim count As Integer
Do Until count = 20
mainLst.Add(New Things With {.Prop1 = count.ToString, _
.Prop2 = (count + 1).ToString, _
.Prop3 = (count + 2).ToString, _
.Prop4 = (count + 3).ToString})
count += 1
Loop
' Need to dynamically pick properties From mainLst into subLst.
' The commented code below wont compile but demonstrates what I'm trying to do
' can this be done without looping?
'Dim propNameA As String = "Prop1" ' Dynamically specify a property name
'Dim propNameB As String = "Prop4"
'Dim subLst = From mainItem In mainLst
' Select New SubThing() With {.P1 = mainItem.propNameA, .P2 = mainItem.propNameB}
' This code below compiles but lacks the dynamics I need?
Dim subLst = From mainItem In mainLst
Select New SubThing() With {.P1 = mainItem.Prop1, .P2 = mainItem.Prop4}
End Sub
The most direct approach would be to use CallByName (MSDN Link). I'm assuming your example is a simplified version of what you're really working with, but it seems like an even better approach would be to get rid of your Prop1, Prop2, ... string properties and just use a List(Of String) which you can then just index into, without having to frankenstein together the property names with an index value. Example:
Public Property Props As List(Of String)
'...
Dim subLst = From mainItem In mainLst
Select New SubThing() With {.P1 = mainItem.Props(1), .P2 = mainItem.Props(4)}
Not really sure what your exact use case is from your example, but hopefully this points you in the right direction.
Here's an example using reflection as helrich# suggested. (you have to Imports System.Reflection at the top of your .vb file)
1) Naive console outputting example:
Dim thingType As Type = GetType(Things)
Dim prop1Property As PropertyInfo = thingType.GetProperty("Prop1")
Dim thingInstance As Things = New Things()
thingInstance.Prop1 = "My Dynamically Accessed Value"
Dim prop1Value = prop1Property.GetValue(thingInstance).ToString()
Console.WriteLine(prop1Value)
2) Adapted to your example ("probably" works, haven't tested it all):
Dim propNameA As String = "Prop1" ' Dynamically specify a property name
Dim propNameB As String = "Prop4"
Dim propAPropInfo As PropertyInfo = GetType(Things).GetProperty(propNameA)
Dim propBPropInfo As PropertyInfo = GetType(Things).GetProperty(propNameB)
Dim subLst = From mainItem In mainLst
Select New SubThing() With {.P1 = propAPropInfo.GetValue(mainItem).ToString(), .P2 = propBPropInfo.GetValue(mainItem).ToString()}
Option Strict On
Option Explicit On
Imports System.Reflection
Module Module1
Private Class SourceClass
Public Property Prop1 As String
Public Property Prop2 As String
Public Property Prop3 As String
Public Property Prop4 As String
End Class
Private Class SubClass
Public Property P1 As String
Public Property P2 As String
End Class
Sub Main()
Dim mainLst As New List(Of SourceClass)
Dim count As Integer
Do Until count = 20 ' create source list
mainLst.Add(New SourceClass With {.Prop1 = count.ToString, _
.Prop2 = (count + 1).ToString, _
.Prop3 = (count + 2).ToString, _
.Prop4 = (count + 3).ToString})
count += 1
Loop
Dim propAInfo As PropertyInfo = GetType(SourceClass).GetProperty("Prop1") ' Dynamically specify a property name
Dim propBInfo As PropertyInfo = GetType(SourceClass).GetProperty("Prop3")
' create a list of SubClass from SourceClass
Dim subLst = From mainItem In mainLst Select New SubClass() _
With {.P1 = propAInfo.GetValue(mainItem, Nothing).ToString, _
.P2 = propBInfo.GetValue(mainItem, Nothing).ToString}
count = 0
Do Until count = subLst.Count
Debug.WriteLine(subLst(count).P1 & "~" & subLst(count).P2)
count += 1
Loop
End Sub
End Module

Get the name of the object passed in a byref parameter vb.net

How can I get the name of the object that was passed byref into a method?
Example:
Dim myobject as object
sub mymethod(byref o as object)
debug.print(o.[RealName!!!!])
end sub
sub main()
mymethod(myobject)
'outputs "myobject" NOT "o"
end sub
I'm using this for logging. I use one method multiple times and it would be nice to log the name of the variable that I passed to it. Since I'm passing it byref, I should be able to get this name, right?
For minitech who provided the answer:
This would give you the parameter name in the method and it's type, but not the name of the variable that was passed byref.
using system.reflection
Dim mb As MethodBase = MethodInfo.GetCurrentMethod()
For Each pi As ParameterInfo In mb.GetParameters()
Debug.Print("Parameter: Type={0}, Name={1}", pi.ParameterType, pi.Name)
Next
If you put that in "mymethod" above you'd get "o" and "Object".
That's impossible. Names of variables are not stored in IL, only names of class members or namespace classes. Passing it by reference makes absolutely zero difference. You wouldn't even be able to get it to print out "o".
Besides, why would you ever want to do that?
Alternatively you could get the 'Type' of the object using reflection.
Example: (Use LinqPad to execute)
Sub Main
Dim myDate As DateTime = DateTime.Now
MyMethod(myDate)
Dim something As New Something
MyMethod(something)
End Sub
Public Class Something
Public Sub New
Me.MyProperty = "Hello"
End Sub
Public Property MyProperty As String
End Class
Sub MyMethod(Byref o As Object)
o.GetType().Name.Dump()
End Sub
Sorry to say, but this is your solution. I left (ByVal o As Object) in the method signature in case you're doing more with it.
Sub MyMethod(ByVal o As Object, ByVal name As String)
Debug.Print(name)
End Sub
Sub Main()
MyMethod(MyObject, "MyObject")
End Sub
Alternatively you could create an interface, but this would only allow you to use MyMethod with classes you design. You can probably do more to improve it, but as this code stands you can only set the RealName at creation.
Interface INamedObject
Public ReadOnly Property RealName As String
End Interface
Class MyClass
Implements INamedObject
Public Sub New(ByVal RealName As String)
_RealName = RealName
End Sub
Private ReadOnly Property RealName As String Implements INamedObject.RealName
Get
Return _RealName
End Get
End Property
Private _RealName As String
End Class
Module Main
Sub MyMethod(ByVal o As INamedObject)
Debug.Print(o.RealName)
End Sub
Sub Main()
Dim MyObject As New MyClass("MyObject")
MyMethod(MyObject)
End Sub
End Module
If your program is still in the same place relative to the code that made it, this may work:
' First get the Stack Trace, depth is how far up the calling tree you want to go
Dim stackTrace As String = Environment.StackTrace
Dim depth As Integer = 4
' Next parse out the location of the code
Dim delim As Char() = {vbCr, vbLf}
Dim traceLine As String() = stackTrace.Split(delim, StringSplitOptions.RemoveEmptyEntries)
Dim filePath As String = Regex.Replace(traceLine(depth), "^[^)]+\) in ", "")
filePath = Regex.Replace(filePath, ":line [0-9]+$", "")
Dim lineNumber As String = Regex.Replace(traceLine(depth), "^.*:line ", "")
' Now read the file
Dim program As String = __.GetStringFromFile(filePath, "")
' Next parse out the line from the class file
Dim codeLine As String() = program.Split(delim)
Dim originLine As String = codeLine(lineNumber * 2 - 2)
' Now get the name of the method doing the calling, it will be one level shallower
Dim methodLine As String = Regex.Replace(traceLine(depth - 1), "^ at ", "")
Dim methodName = Regex.Replace(methodLine, "\(.*\).*$", "")
methodName = Regex.Replace(methodName, "^.*\.", "")
' And parse out the variables from the method
Dim variables As String = Regex.Replace(originLine, "^.*" & methodName & "\(", "")
variables = Regex.Replace(variables, "\).*$", "")
You control the depth that this digs into the stack trace with the depth parameter. 4 works for my needs. You might need to use a 1 2 or 3.
This is the apparently how Visual Basic controls handle the problem.
They have a base control class that in addition to any other common properties these controls may have has a name property.
For Example:
Public MustInherit Class NamedBase
Public name As String
End Class
Public Class MyNamedType
Inherits NamedBase
public Value1 as string
public Value2 as Integer
End Class
dim x as New MyNamedType
x.name = "x"
x.Value1 = "Hello, This variable is name 'x'."
x.Value2 = 75
MySubroutine(x)
public sub MySubroutine(y as MyNamedType)
debug.print("My variable's name is: " & y.name)
end sub
The output in the intermediate window should be:
My variable's name is: x