Select Variable by its Name and Set - vb.net

Module MyModule
Public Property alpha As String
Public Property beta As String
Public Property charlie As String
End MyModule
Public Class MyClass
Sub New()
Dim variableName as String = "beta"
MyModule.GetField(variableName) = "new value"
End Sub
End Class
I'm looking for a way to set the value of a variable in the module. But only the variable with the given variable name. The example above obviously does not work, but it provides an idea of my goal.
Imports System.Reflection
Public Class Form1
Sub New()
SetVariableByVariableName("beta", "new value")
End Sub
Public Sub SetVariableByVariableName(ByVal variableName As String, ByVal value As String)
Dim myFieldInfo As FieldInfo = GetType(MyModule).GetField(variableName, BindingFlags.Public Or BindingFlags.Static)
If myFieldInfo IsNot Nothing Then
myFieldInfo.SetValue(Nothing, value)
Console.WriteLine($"The value of {variableName} is now: {GetType(MyModule).GetField(variableName).GetValue(Nothing)}")
Else
Console.WriteLine($"Error: {variableName} does not exist in MyModule.")
End If
End Sub
End Class
Module MyModule
Public Property alpha As String
Public Property beta As String
Public Property charlie As String
End Module

You are on the right track with using reflection and FieldInfo to access the field in the MyModule module by its name. To set the value of the field, you need to pass two parameters to the SetValue method: the instance of the object (in this case, Nothing since the field is static), and the new value to assign to the field.
Here's how you can modify your SetVariableByVariableName method to achieve your goal:
Public Sub SetVariableByVariableName(ByVal variableName As String, ByVal value As String)
Dim myFieldInfo As FieldInfo = GetType(MyModule).GetField(variableName, BindingFlags.Public Or BindingFlags.Static)
If myFieldInfo IsNot Nothing Then
myFieldInfo.SetValue(Nothing, value)
Console.WriteLine($"The value of {variableName} is now: {MyModule.GetType().GetField(variableName).GetValue(Nothing)}")
Else
Console.WriteLine($"Error: {variableName} does not exist in MyModule.")
End If
End Sub
This method takes two parameters: the name of the variable you want to modify, and the new value you want to assign to it. It first retrieves the FieldInfo object for the variable with the given name from the MyModule module, using the GetField method of the Type class. Note that we have to specify the BindingFlags.Public and BindingFlags.Static flags to indicate that we want to access a public static field.
Next, it checks if the field exists. If it does, it calls the SetValue method on the FieldInfo object to assign the new value to the field. Finally, it retrieves the value of the field again to confirm that the new value has been assigned correctly, and prints it to the console.
If the field does not exist, it prints an error message to the console.
You can call this method from your MyClass constructor like this:
Public Class MyClass
Sub New()
SetVariableByVariableName("beta", "new value")
End Sub
End Class
This should set the value of the beta variable in the MyModule module to "new value" when a new instance of MyClass is created.

Here's an example based on my previous answer here:
MyModule:
Module MyModule
Public Property alpha As String
Public Property beta As String = "default beta"
Public Property charlie As String
End Module
Module1, which accesses the above "by name":
Imports System.Reflection
Module Module1
Sub Main()
Console.WriteLine("Module Property accessed directly: " & MyModule.beta)
Console.WriteLine("Module Property accessed 'by name': " & GetModulePropertyByName("MyModule", "beta"))
Console.WriteLine("Changing value...")
SetModulePropertyByName("MyModule", "beta", "Can you hear me now?")
Console.WriteLine("Module Property accessed directly: " & MyModule.beta)
Console.WriteLine("Module Property accessed 'by name': " & GetModulePropertyByName("MyModule", "beta"))
Console.WriteLine()
Console.Write("Press any key to quit...")
Console.ReadKey()
End Sub
Public Sub SetModulePropertyByName(ByVal moduleName As String, ByVal moduleField As String, ByVal value As String)
Dim myType As Type = Nothing
Dim myModule As [Module] = Nothing
For Each x In Assembly.GetExecutingAssembly().GetModules
For Each y In x.GetTypes
If y.Name.ToUpper = moduleName.ToUpper Then
myType = y
Exit For
End If
Next
If Not IsNothing(myType) Then
Exit For
End If
Next
If Not IsNothing(myType) Then
Dim flags As BindingFlags = BindingFlags.IgnoreCase Or BindingFlags.NonPublic Or BindingFlags.Public Or BindingFlags.Static Or BindingFlags.Instance
Dim pi As PropertyInfo = myType.GetProperty(moduleField, flags)
If Not IsNothing(pi) Then
pi.SetValue(Nothing, value)
End If
End If
End Sub
Public Function GetModulePropertyByName(ByVal moduleName As String, ByVal moduleField As String) As Object
Dim O As Object = Nothing
Dim myType As Type = Nothing
Dim myModule As [Module] = Nothing
For Each x In Assembly.GetExecutingAssembly().GetModules
For Each y In x.GetTypes
If y.Name.ToUpper = moduleName.ToUpper Then
myType = y
Exit For
End If
Next
If Not IsNothing(myType) Then
Exit For
End If
Next
If Not IsNothing(myType) Then
Dim flags As BindingFlags = BindingFlags.IgnoreCase Or BindingFlags.NonPublic Or BindingFlags.Public Or BindingFlags.Static Or BindingFlags.Instance
Dim pi As PropertyInfo = myType.GetProperty(moduleField, flags)
If Not IsNothing(pi) Then
O = pi.GetValue(Nothing)
End If
End If
Return O
End Function
End Module
Output:
Module Property accessed directly: default beta
Module Property accessed 'by name': default beta
Changing value...
Module Property accessed directly: Can you hear me now?
Module Property accessed 'by name': Can you hear me now?
Press any key to quit...

Related

Method to access class members by item numbers

Sorry for newbie question. I'm new to VBA class.
I created a cRanges class which is basically a collection of another custom object. And I defined a Item method for it. But I couldn't understand why the Item method of cRanges class didn't work?
Here's my code:
' CLASS MODULE - cRange
' Member variables
Private m_Low As String
Private m_High As String
' Properties
Property Get low() As String
low = m_Low
End Property
Property Get high() As String
high = m_High
End Property
Property Let low(s As String)
m_Low = s
End Property
Property Let high(s As String)
m_High = s
End Property
------------------
' CLASS MODULE - cRanges
' Member variables
Private m_Ranges As New Collection
Private r As New cRange
' Methods
Public Sub Add(r As cRange)
m_Ranges.Add r
End Sub
Public Sub Remove(r As cRange)
m_Ranges.Remove r
End Sub
Public Function Count() As Long
Count = m_Ranges.Count
End Function
Public Function Item(i As Integer) As cRange
If i > 0 And i <= m_Ranges.Count Then Set Items = m_Ranges.Item(i)
End Function
--------------------
Sub main()
Dim r As New cRange
Dim rr As New cRanges
r.low = "2"
r.high = "9"
rr.Add r
Debug.Print r.low
Debug.Print r.high
Debug.Print rr.Count
Debug.Print rr.Item(1).high '<-- Object variable or with block variable not set
End Sub
Thanks!
...................................................................................

How do I reference a DIM Name (not value) in a function?

I have a Dim publicly declared in my Form, how would I set/change the value of this Dim in a function without manually calling it like this: Test = "New Val"?
I would need it to be something like this:
Public Class Form2
Dim Test As String = "I do not want to read this value in the function,
I do however need to change this value"
Private Function PassingDimName(ByVal DimName As dim)
'Having it like this gives the following error:
'"Keyword does not name a type"
DimName = "Dims new value"
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
PassingDimName(Test)
End Sub
End Class
Use System.Reflection.GetField (String, BindingFlags) to access a field by name
Here are some functions for setting and getting field values:
Public Sub SetValue(name As String, value As Object)
Dim fi As FieldInfo = Me.GetType().GetField(name, BindingFlags.NonPublic Or BindingFlags.Instance)
fi.SetValue(Me, value)
End Sub
Public Function GetValue(name As String) As Object
Dim fi As FieldInfo = Me.GetType().GetField(name, BindingFlags.NonPublic Or BindingFlags.Instance)
Return fi.GetValue(Me)
End Function
Note: the BindingFlags are specific to your application, where you have declared a private field (Dim Test As ...). See BindingFlags Enumeration if you want to use something other than a private field.
In your specific implementation, you could set a new string to the field in the button click like this
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
SetValue("Test1", "new value")
End Sub
A minimal, complete example to demonstrate how it works could look like this
Dim f2 = New Form2()
Console.WriteLine(f2.GetValue("Test1"))
Console.WriteLine(f2.GetValue("Test2"))
f2.SetValue("Test1", "new value")
f2.SetValue("Test2", 1)
Console.WriteLine(f2.GetValue("Test1"))
Console.WriteLine(f2.GetValue("Test2"))
with the form
Public Class Form2
Dim Test1 As String = "initial value"
Dim Test2 As Integer = 0
Public Sub SetValue(name As String, value As Object)
Dim fi As FieldInfo = Me.GetType().GetField(name, BindingFlags.NonPublic Or BindingFlags.Instance)
fi.SetValue(Me, value)
End Sub
Public Function GetValue(name As String) As Object
Dim fi As FieldInfo = Me.GetType().GetField(name, BindingFlags.NonPublic Or BindingFlags.Instance)
Return fi.GetValue(Me)
End Function
End Class
output:
initial value
0
new value
1
Another note: when setting the field, you aren't guaranteed to know the type. Trying f2.SetValue("Test2", "new value") would compile but would fail at runtime when trying to convert a string to an integer. If you know all your fields are strings, then it may be better to declare Public Sub SetValue(name As String, value As String) and Public Function GetValue(name As String) As String with the appropriate casts.
If you just want to change the value of the variable then change your function to this.
Private Sub PassingDimName(ByRef DimName As String)
'Having it like this gives the following error:
'"Keyword does not name a type"
DimName = "Dims new value"
End Sub

EF marking entities as modified with no changes

Using proxies in EF6.1.3 with the following code (VB.NET): -
Dim DB As New BMContext
Dim sl = DB.StockLevels.First
Dim ee = (From e In DB.ChangeTracker.Entries Where e.Entity Is sl).Single
sl.Level = sl.Level
Checking ee.State before the final line correctly gives a state of Unmodified. After that line it shows as Modified even though the property has been set to what it already was. This even triggers an UPDATE when I call SaveChanges!
Data class code: -
Public Class StockLevel
Public Overridable Property ID As Integer
Public Overridable Property Level As Integer?
End Class
Obviously my actual code is rather a lot more complex as this example is pretty pointless other than demonstrating the problem.
"change-tracking proxies mark a property as modified whenever any value is written to it."
From source
Basically, since you're assigning a value to this property (even though it is the exact same value), you are receiving a Modified state.
I've ended up writing this to call before SaveChanges, although I find it a bit ridiculous that I need to...
Public Class DbEntityModification
Public ReadOnly Property FieldName As String
Public ReadOnly Property OriginalValue As Object
Public ReadOnly Property CurrentValue As Object
Public Sub New(FieldName As String, OriginalValue As Object, CurrentValue As Object)
_FieldName = FieldName
_OriginalValue = OriginalValue
_CurrentValue = CurrentValue
End Sub
End Class
<Extension()> Public Function GetChangedValues(e As DbEntityEntry) As IDictionary(Of String, DbEntityModification)
Dim ret As New Dictionary(Of String, DbEntityModification)
For Each propname In e.CurrentValues.PropertyNames
Dim nv = e.CurrentValues.Item(propname)
Dim ov = e.OriginalValues.Item(propname)
Dim Changed = False
If ov Is Nothing Then
Changed = nv IsNot Nothing
ElseIf Not ov.Equals(nv) Then
Changed = True
End If
If Changed Then
Dim m As New DbEntityModification(propname, ov, nv)
ret.Add(propname, m)
End If
Next
Return ret
End Function
<Extension()> Public Sub MarkUnchangedAnyUnchangedEntities(ees As IEnumerable(Of DbEntityEntry))
For Each ee In ees
If ee.State = EntityState.Modified Then
If GetChangedValues(ee).Keys.Count = 0 Then
ee.State = EntityState.Unchanged
End If
End If
Next
End Sub
<Extension()> Public Sub MarkUnchangedAnyUnchangedEntities(context As DbContext)
context.ChangeTracker.Entries.MarkUnchangedAnyUnchangedEntities()
End Sub

Extracting property values from a dictionary

I am attempting to write a subroutine that will deserialize a dictionary from a .ser file (this bit works fine) and then repopulate several lists from this dictionary (this is the bit I cannot do).
The dictionary contains objects (I think) of a custom class I wrote called "Photo Job" which has properties such as ETA, notes, medium etc. (Declared as such)
Dim photoJobs As New Dictionary(Of String, PhotoJob)
In short, I want to be able to extract every entry of each specific property into an separate arrays (one for each property) and I can go from there.
Any help would be appreciated, I may be going about this completely the wrong way, I'm new to VB. The relevant code is below:
Photo Job Class:
<Serializable()> _Public Class PhotoJob
Private intStage As Integer 'Declare all local private variables
Private ID As String
Private timeLeft As Integer
Private material As String '
Private note As String
Private path As String
Private finished As Boolean = False
'Declare and define properties and methods of the class
Public Property productionStage() As Integer
Get
Return intStage
End Get
Set(ByVal Value As Integer)
intStage = Value
End Set
End Property
Public Property photoID() As String
Get
Return ID
End Get
Set(ByVal Value As String)
ID = Value
End Set
End Property
Public Property ETA() As Integer
Get
Return timeLeft
End Get
Set(ByVal Value As Integer)
timeLeft = Value
End Set
End Property
Public Property medium() As String
Get
Return material
End Get
Set(ByVal Value As String)
material = Value
End Set
End Property
Public Property notes() As String
Get
Return note
End Get
Set(ByVal Value As String)
note = Value
End Set
End Property
Public Property imagePath() As String
Get
Return path
End Get
Set(ByVal Value As String)
path = Value
End Set
End Property
Public Property complete() As Boolean
Get
Return finished
End Get
Set(value As Boolean)
finished = value
End Set
End Property
Public Sub nextStage()
If intStage < 4 Then
intStage += 1
ElseIf intStage = 4 Then
intStage += 1
finished = True
End If
End Sub
End Class
Subroutines involved in de/serialisation:
Private Sub BackupAllToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles BackupAllToolStripMenuItem.Click
Dim formatter As New BinaryFormatter
Dim backupFile As New FileStream(Strings.Replace(Strings.Replace(Now, ":", "_"), "/", ".") & ".ser", FileMode.Create, FileAccess.Write, FileShare.None)
formatter.Serialize(backupFile, photoJobs)
backupFile.Close()
MsgBox("Collection saved to file")
End Sub
Private Sub RestoreFromFileToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles RestoreFromFileToolStripMenuItem.Click
With OpenFileDialog 'Executes the following sets/gets/methods of the OpenFileDialog
.FileName = ""
.Title = "Open Image File"
.InitialDirectory = "c:\"
.Filter = "Serial Files(*.ser)|*ser"
.ShowDialog()
End With
Dim backupPathStr As String = OpenFileDialog.FileName
Dim deSerializer As New BinaryFormatter
Dim backupFile As New FileStream(backupPathStr, FileMode.Open)
photoJobs = deSerializer.Deserialize(backupFile)
backupFile.Close()
End Sub
From what I can see using the autos menu, the saving/restoring of the dictionary works just fine.
First, if you are using VS2010+, you can greatly reduce boilerplate code using autoimplemented properties:
<Serializable()>
Public Class PhotoJob
Public Property productionStage() As Integer
Public Property photoID() As String
Public Property ETA() As Integer
etc
End Class
That is all that is needed, all the boilerplate code is handled for you. Second, with this line:
photoJobs = deSerializer.Deserialize(backupFile)
Your deserialized photojobs will be a generic Object, not a Dictionary. You should turn on Option Strict so VS will enforce these kinds of errors. This is how to deserialize to Type:
Using fs As New FileStream(myFileName, FileMode.Open)
Dim bf As New BinaryFormatter
PhotoJobs= CType(bf.Deserialize(fs), Dictionary(Of String, PhotoJob))
End Using
Using closes and disposes of the stream, CType converts the Object returned by BF to an actual dictionary
To work with the Dictionary (this has nothing to do with Serialization) you need to iterate the collection to get at the data:
For Each kvp As KeyValuePair(Of String, PhotoJob) In PhotoJobs
listbox1.items.Add(kvp.value.productionStage)
listbox2.items.Add(kvp.value.ETA)
etc
Next
The collection is a made of (String, PhotoJob) pairs as in your declaration, and when you add them to the collection. They comeback the same way. kvp.Key will be the string key used to identify this job in the Dictionary, kvp.Value will be a reference to a PhotoJobs object.
As long as VS/VB knows it is a Dictionary(of String, PhotoJob), kvp.Value will act like an instance of PhotoJob (which it is).

VB.NET CType: How do I use CType to change an object variable "obj" to my custom class that I reference using a string variable like obj.GetType.Name?

The code below works for the class that I hard coded "XCCustomers" in my RetrieveIDandName method where I use CType. However, I would like to be able to pass in various classes and property names to get the integer and string LIST returned. For example, in my code below, I would like to also pass in "XCEmployees" to my RetrieveIDandName method. I feel so close... I was hoping someone knew how to use CType where I can pass in the class name as a string variable.
Note, all the other examples I have seen and tried fail because we are using Option Strict On which disallows late binding. That is why I need to use CType.
I also studied the "Activator.CreateInstance" code examples to try to get the class reference instance by string name but I was unable to get CType to work with that.
When I use obj.GetType.Name or obj.GetType.FullName in place of the "XCCustomers" in CType(obj, XCCustomers)(i)
I get the error "Type 'obj.GetType.Name' is not defined" or "Type 'obj.GetType.FullName' is not defined"
Thanks for your help.
Rick
'+++++++++++++++++++++++++++++++
Imports DataLaasXC.Business
Imports DataLaasXC.Utilities
Public Class ucCustomerList
'Here is the calling method:
Public Sub CallingSub()
Dim customerList As New XCCustomers()
Dim customerIdAndName As New List(Of XCCustomer) = RetrieveIDandName(customerList, "CustomerId", " CustomerName")
'This code below fails because I had to hard code “XCCustomer” in the “Dim item...” section of my RetrieveEmployeesIDandName method.
Dim employeeList As New XCEmployees()
Dim employeeIdAndName As New List(Of XCEmployee) = RetrieveIDandName(employeeList, "EmployeeId", " EmployeeName")
'doing stuff here...
End Sub
'Here is the method where I would like to use the class name string when I use CType:
Private Function RetrieveIDandName(ByVal obj As Object, ByVal idPropName As String, ByVal namePropName As String) As List(Of IntStringPair)
Dim selectedItems As List(Of IntStringPair) = New List(Of IntStringPair)
Dim fullyQualifiedClassName As String = obj.GetType.FullName
Dim count As Integer = CInt(obj.GetType().GetProperty("Count").GetValue(obj, Nothing))
If (count > 0) Then
For i As Integer = 0 To count - 1
'Rather than hard coding “XCCustomer” below, I want to use something like “obj.GetType.Name”???
Dim Item As IntStringPair = New IntStringPair(CInt(CType(obj, XCCustomers)(i).GetType().GetProperty("CustomerId").GetValue(CType(obj, XCCustomers)(i), Nothing)), _
CStr(CType(obj, XCCustomers)(i).GetType().GetProperty("CustomerName").GetValue(CType(obj, XCCustomers)(i), Nothing)))
selectedItems.Add(Item)
Next
End If
Return selectedItems
End Function
End Class
'+++++++++++++++++++++++++++++++
' Below are the supporting classes if you need to see what else is happening:
Namespace DataLaasXC.Utilities
Public Class IntStringPair
Public Sub New(ByVal _Key As Integer, ByVal _Value As String)
Value = _Value
Key = _Key
End Sub
Public Property Value As String
Public Property Key As Integer
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCCustomer
Public Property CustomerId As Integer
Public Property CustomerName As String
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCCustomers
Inherits List(Of XCCustomer)
Public Sub New()
PopulateCustomersFromDatabase()
End Sub
Public Sub New(ByVal GetEmpty As Boolean)
End Sub
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCEmployee
Public Property EmployeeId As Integer
Public Property EmployeeName As String
End Class
End Namespace
'+++++++++++++++++++++++++++++++
Namespace DataLaasXC.Business
Public Class XCEmployees
Inherits List(Of XCEmployee)
Public Sub New()
PopulateEmployeesFromDatabase()
End Sub
Public Sub New(ByVal GetEmpty As Boolean)
End Sub
End Class
End Namespace
From MSDN
CType(expression, typename)
. . .
typename : Any expression that is legal
within an As clause in a Dim
statement, that is, the name of any
data type, object, structure, class,
or interface.
This is basically saying you can't use CType dynamically, just statically. i.e. At the point where the code is compiled the compiler needs to know what typename is going to be.
You can't change this at runtime.
Hope this helps.
Since List(Of T) implements the non-generic IList interface, you could change your function declaration to:
Private Function RetrieveIDandName(ByVal obj As System.Collections.IList, ByVal idPropName As String, ByVal namePropName As String) As List(Of IntStringPair)
And then your troublesome line would become (with also using the property name parameters):
Dim Item As IntStringPair = New IntStringPair(CInt(obj(i).GetType().GetProperty(idPropName).GetValue(obj(i), Nothing)), _
CStr(obj(i).GetType().GetProperty(namePropName).GetValue(obj(i), Nothing)))
Of course, you could still have the first parameter by Object, and then attempt to cast to IList, but that's up to you.
ctype is used to convert in object type.