How To Iterate Over Members of a Structure Array in VB.net Using Member Function - vb.net

I have a vb.net enumeration that looks like this:
' define enumeration for keypad states
Enum KeyPadState
KEYPAD_NO ' no keypad
KEYPAD_UC ' upper case keypad
KEYPAD_LC ' lower case keypad
KEYPAD_NU ' numeric keypad
KEYPAD_SY ' symbol keypad
End Enum
I then defined a structure element to be used to translate the members of the above enumeration from enumeration values to string values and back again. The declared structure looks like below. Note the member functions that I have tried to insert. The "New" one is working.
' define keypad type look up structure
Private Structure KeyPadXlat
Dim KeyPadEnum As KeyPadState
Dim KeyPadStr As String
' initializer subroutine
Public Sub New(nKeyPadEnum As KeyPadState, nKeyPadStr As String)
KeyPadEnum = nKeyPadEnum
KeyPadStr = nKeyPadStr
End Sub
' translate string to enum
Public Function ToEnum(xKeyPadStr As String) As KeyPadState
For Each item As KeyPadXlat In ????
Next
End Function
' translate enum to string
Public Function ToStr(xKeyPadEnum As KeyPadState) As String
End Function
End Structure
The actual instance of the structure array is shown below with its initializer code.
Dim KeyPadLookUp() As KeyPadXlat = { _
New KeyPadXlat(KeyPadState.KEYPAD_NO, "KEYPAD_NO"), _
New KeyPadXlat(KeyPadState.KEYPAD_UC, "KEYPAD_UC"), _
New KeyPadXlat(KeyPadState.KEYPAD_LC, "KEYPAD_LC"), _
New KeyPadXlat(KeyPadState.KEYPAD_NU, "KEYPAD_NU"), _
New KeyPadXlat(KeyPadState.KEYPAD_SY, "KEYPAD_SY") _
}
So my question is with regard to the member functions I am trying to create to translate back and forth between the enumeration value and the string value. I have copied one of them here again for reference:
' translate string to enum
Public Function ToEnum(xKeyPadStr As String) As KeyPadState
For Each item As KeyPadXlat In ????
Next
End Function
What I need help with is how to write the code for the For Each loop so that it iterates across all of the elements of the structure array when being in a member function.

To be honest, you really don't need all that code. This should do it nicely.
Enum KeyPadState
KEYPAD_NO ' no keypad
KEYPAD_UC ' upper case keypad
KEYPAD_LC ' lower case keypad
KEYPAD_NU ' numeric keypad
KEYPAD_SY ' symbol keypad
End Enum
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim state As KeyPadState
state = KeyPadState.KEYPAD_LC
'this line will assign the name of the enum `state` to a string called `tempstring`
'It's hardly worth encapsulating it into a function so I've left it as is
'But if you want to provide consistent code, it would be better to.
Dim tempstring As String
tempstring = [Enum].GetName(GetType(KeyPadState), state)
Dim anyString As String = "KEYPAD_UC"
Dim tempState As KeyPadState
'the following line will try to parse `anyString` to an enum value of the same type as the variable to be assigned.
'In this case `state`
tempState = ParseToKeypadState(anyString)
End Sub
Private Function ParseToKeypadState(tempString As String) As KeyPadState
Dim returnValue As KeyPadState
If Not [Enum].TryParse(tempString, returnValue) Then
'handle parsing error here
End If
Return returnValue
End Function

There's all sorts wrong with your code there.
Firstly, I'd suggest that your naming conventions are poor. If you do as is done throughout the .NET Framework then you enumeration would look like this:
Enum KeyPadState
None
UpperCase
LowerCase
Numeric
Symbol
End Enum
That's clear and self-documenting.
Secondly, it is not recommended to use abbreviations like "Xlat". That's meaningless to anyone without prior knowledge. Is it so onerous to write "Translate" and then let Intellisense find it whenever you need to use it in code?
As for the implementation of your class, why does it need any methods at all? You are passing in the KeyPadState value and the text representation when you create an instance so what is there for those methods to do? Your structure should simply be a constructor and two properties:
Private Structure KeyPadStateTranslation
Public ReadOnly Property Value As KeyPadState
Public ReadOnly Property Text As String
Public Sub New(value As KeyPadState, text As String)
Me.Value = value
Me.Text = text
End Sub
End Structure
The property values are set when the instance is created and they are retrieved via the properties. Everything also has an appropriate name.
That said, you don't even need to provide the text because you can simply call ToString on the value to get it:
Private Structure KeyPadStateTranslation
Public ReadOnly Property Value As KeyPadState
Public ReadOnly Property Text As String
Public Sub New(value As KeyPadState)
Me.Value = value
Me.Text = value.ToString()
End Sub
End Structure
Also, the fact that that structure is declared Private is an issue. That indicates that it is declared inside another type. That's not right. Structures are first-class types, just like classes, so they belong in their own dedicated code file, just like classes.
What's the point of that structure at all though? You'd still have to loop through your array to find an instance that matches either a value or some text so it doesn't really help. A Dictionary might be better but you may as well just call ToString on a value if you need to convert that way and use Enum.Parse or .TryParse when you need to go the other way.

Related

How to find the index of an object in a list of objects? VB.Net

I am creating a method FindPerson which searches for a given name in a list of objects and returns the index in the list of the object with this name if found, otherwise it returns -1.
Public Class TPerson
Private Name As String
Private Address As String
Private Age As Integer
Public Sub New()
Name = "x"
Address = "x"
Age = 0
End Sub
……
End Class
Public Class TGroup
Private Group As List(Of TPerson)
Private GroupSize As Integer
Public Sub New(size As Integer)
GroupSize = size
Group = New List(Of TPerson)
End Sub
Public Sub FindPerson(findname As String)
Dim index As Integer
index = Group.FindIndex(findname) 'error
End Sub
End Class
The output should be an index in the list, however when I run the program I get the error: BC30311 Value of type 'String' cannot be converted to 'Predicate(Of TPerson)'
I am not quite sure how to fix this any help will be appreciated
How exactly do you expect the FindIndex method to know what to do with that String that you are passing in? You seem to assume that it will know that it represents a name and that it needs to match an item by Name property but how do you think it's going to do that? Why do you think it would match on Name rather than Address?
As the error message says, you need to provide a Predicate which is a delegate that takes an object of type T and returns a Boolean. In your case, T is TPerson and the Boolean needs to indicate whether findname matches its Name property. The simplest way to do that is with a Lambda expression:
Dim index = Group.FindIndex(Function(person) person.Name = findname)
You could do it with a named method and a delegate if you wanted to but it would be more long-winded and it would mean getting the findname value in by some convoluted means. If you read the documentation for the FindIndex method (which you should have done before posting here) you can find an example of that sort of thing.

VBA Complicated Getter, Setter syntax

Hi I'm rather new to VBA I need to create an object with relativelly complicated Getter and Setter. To do this I am constantly checking with MSDN but clearly I am not understanding something because VBE keep highlighting lines starting and closing: Property (it appaently needs Get or Let??), Get(it apparently needs identifier), Let(it apparently needs identifier as well).
But I am trying to follow more concise notation where Get and Let methods are within a Property Statement which is used by Microsoft in its examples(see link above).
Can someone tell me where is my syntax wrong(or Microsoft's Documentation for that matter)???
Thank you
Private Matrix() As Vector
Property Transition()
Public Get(Old_S As String, New_S As String, Period As Integer) As Double
' Some Code
Return Matrix(Column, Row).Value(Period)
End Get
Public Let(Old_S As String, New_S As String, Vector_String As String)
' Some Code
Matrix(Row, Column).Value = Vector_String
End Let
End Property
You are reading the documentation for VB.NET. That's why you are confused. The syntax for properties in VBA is different. In VBA, the Get and Let for a property are not grouped together. They need to be listed separately, essentially like two separate methods:
Private mMyProperty As String
Public Property Get MyProperty() As String
MyProperty = mMyProperty
End Property
Public Property Let Transition(Value As String)
mMyProperty = Value
End Property
For VBA reference material, try starting here.
Your issue is that you are reading .Net help files! :)
Assuming you have a valid Vector class, your properties need to bde defined like this:
Private Matrix() As Vector
Public Property Get Transition(Old_S As String, New_S As String, Period As Integer) As String
' Some Code
Transition = Matrix(Column, Row).Value(Period)
End Property
Public Property Let Transition(Old_S As String, New_S As String, Period As Integer, Vector_String As String)
' Some Code
Matrix(Row, Column).Value = Vector_String
End Property
Note that the argument list for both procedures must match except that the Letter has an additional argument of the same type that the Getter returns.
It is also slightly unusual to have a Getter property that accepts arguments in VBA - that would more usually be implemented as a method.
Looks like you're using VB.Net syntax in VBA. That's not going to work, as they're entirely different languages. Here is a link to the proper documentation for the VBA Property Keyword.
Here is how you would write it in VBA.
Private Matrix() As Vector
Public Property Get MatrixValue(Old_S As String, New_S As String, Period As Integer) As Double
' Some Code
MatrixValue = Matrix(Column, Row).Value(Period)
End Property
Public Property Let MatrixValue(Old_S As String, New_S As String, Vector_String As String)
' Some Code
Matrix(Row, Column).Value = Vector_String
End Property
But you're going to have trouble with the Get property. You can pass parameters into Get, but it's not exactly intuitive. I think what you're really looking for is a function.
Public Function GetMatrixValue(Old_S As String, New_S As String, Period As Integer) As Double
' Some Code
MatrixValue = Matrix(Column, Row).Value(Period)
End Function

How to instantiate Class object with varying number of property values

Been working a lot with custom classes lately and I love the power you can have with them but I have come across something that I'm not able to solve and/or find anything helpful online.
I have a list of a class with properties I'm looking to only store information pulled from a database into.
Public Class CustomClass
Public _Values As String
Public _Variables As String
Public ReadOnly Property Values() As String
Get
Return _Values
End Get
End Property
Public ReadOnly Property Variables() As String
Get
Return _Variables
End Get
End Property
Sub New(ByVal values As String, ByVal variables As String)
_Values = values
_Variables = variables
End Sub
End Class
I will be iterating through some database entries, and I'm looking to store them into the appropriate property when I hit them (since I won't have them all available immediately, which is part of my problem). I want to just be able to add either the value or the variable at a time and not both of them, but since I have the sub procedure 'New' passing two arguments, it will always require passing them both. I've found the only way around this is by making them optional fields which I don't feel is the right way to solve this. Is what I'm looking to do possible with a class or would it be simpler by using a structure?
You can overload the constructor:
Friend Class Foo
' using auto-implement props:
Public Property Name As String ' creates a _Name backing field
Public Property Value as Integer
Public Sub New(newN as String, newV as Integer)
' access "hidden" backing fields if you want:
_Name = newN
_Value = newV
End Sub
Public Sub New() ' simple ctor
End Sub
Public Sub New(justName As String)
' via the prop
Name = justName
End Sub
End Class
You now have 3 ways to create the object: with full initialization, partial (name only) or as a blank object. You will often need a "simple constructor" - one with no params - for other purposes: serializers, Collection editors and the like will have no idea how to use the parameterized constructors and will require a simple one.
If rules in the App were that there was no reason for a MyFoo to ever exist unless both Name and Value being defined, implementing only the New(String, Integer) ctor enforces that rule. That is, it is first about the app rules, then about coding convenience.
Dim myFoo As New Foo ' empty one
myFoo.Name = "ziggy" ' we only know part of it
Since the default of string is nothing, you could pass nothing for the value you don't have. IE
Collection.Add(New CustomClass("My Value",Nothing))
Every type has a default, so this works with more than just strings.

How to assign a value to a variable of type Double, that has been passed as Object?

I am trying to assign a value to global variable, which has a Property of type Double. This Property is passed as Object and the assignment fails.
In the example code below, the value is never assigned to the actual object, but only locally:
Public Class Form1
Friend Home As New Building
Private Sub AssignValues() Handles Me.Load
'Objects of different types are added to a list
Dim listObjects As New List(Of Object)
listObjects.Add(Home.Surface)
'All the Objects in listObjects are assigned a value that
'is stored as String
For Each o As Object In listObjects
SetProperty(o, "45.6")
Debug.Print("Surface = " & Home.Surface.ToString)
Next
End Sub
Private Sub SetProperty(ByRef Variable As Object, ByVal Value As String)
Select Case Variable.GetType
Case GetType(Double)
Variable = CDbl(Value)
Case Else
'...
End Select
End Sub
End Class
Public Class Building
Dim _surface As Double = 0
Public Property Surface As Double
Get
Return _surface
End Get
Set(ByVal value As Double)
_surface = value
End Set
End Property
End Class
The program invariably outputs Surface = 0 instead of 45.6. What am I doing wrong?
I tried to pass the Variable as reference, as suggested here, but without success. I also read about using Reflection, but there ought to be something simpler than that...
When your adding home.surface to the list, your adding a copy of the double to the list and then adjusting that copy. Stick a watch on "o" and see how it changes whilst home.surface remains the same.
If you want to use reflection, try something along these lines.
Dim prop As Reflection.PropertyInfo = o.GetType().GetProperty("Surface")
prop.SetValue(o, 45.6)
With Variable.GetType you will get always Object, because this is the type of Variable. What you can do with an Object is converting/casting it into a different type (like Double).
The best way to determine the "original type" from where the Object comes would be including an additional variable telling it. Another option might be converting the given Object into the target Type and see if it is not nothing/does not trigger an error. But this second option is not too accurate, mainly when dealing with "equivalent types" like Doubles/Integers.

why are overloaded private shared function not accessible when called from shared function in same class

Came across something I found interesting and would love an explanation.
Edit
This question is not meant to be answered with what should be done to fix it. I know the fixes. I want an explanation of why the compiler does what it does. Ex. Are the private functions not considered given this scenario?
Problem
I have a class that has a public shared(static) function called WhatIs. WhatIs takes a parameter that has a collection of objects. the code iterates over this collection and calls a WhatIs function that has a parameter matching type of what the object is.
When executed, an InvalidCastException exception is thrown because the execution is trying to call the WhatIs function that started this, not the one for the type provided.
That's weird, but what made it odd to me was when you change the private shared functions to public shared then it works fine.
Even odder, when you explicit cast the object then it works even if the function is private.
What?! someone please explain
Code
the guts:
Public Class House
Public Property Furniture As ICollection(Of Object)
Public Sub New()
Furniture = New List(Of Object)
End Sub
End Class
Public Class Chair
Public Property IsComfortable As Boolean
End Class
Public Class Table
Public Seats As Integer
End Class
Public Class HouseExaminer
Public Shared Function WhatIs(thing As House) As String
Dim isA As String = "a house that contains "
For Each item In thing.Furniture
isA &= WhatIs(item)
Next
Return isA
End Function
Private Shared Function WhatIs(thing As Chair) As String
Return "a " & If(thing.IsComfortable, "comfortable", "uncomfortable") & " chair "
End Function
Private Shared Function WhatIs(thing As Table) As String
Return "a table that seats " & thing.Seats & " iguanas"
End Function
End Class
to test
Imports System.Text
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports stuff
<TestClass()>
Public Class HouseExaminerTests
<TestMethod()>
Public Sub TestWhatIs()
Dim given As New House()
Dim expected As String
Dim actual As String
given.Furniture.Add(New Chair() With {.IsComfortable = True})
given.Furniture.Add(New Table() With {.Seats = 4})
expected = "a house that contains a comfortable chair a table that seats 4 iguanas"
actual = HouseExaminer.WhatIs(given)
Assert.Equals(expected, actual)
End Sub
End Class
result
debug the test and you get this:
InvalidCastException
Method invocation failed because 'Public Shared Function WhatIs(thing As stuff.House) As String' cannot be called with these arguments:
Argument matching parameter 'thing' cannot convert from 'Chair' to 'House'.
These changes make it work but why?!
make em public
change the private shared functions in HouseExaminer to public, rerun test. spoiler, it works
explicitly cast the objects
change them back to private then replace
isA &= WhatIs(item)
with
If TypeOf item Is Chair Then isA &= WhatIs(CType(item, Chair))
If TypeOf item Is Table Then isA &= WhatIs(CType(item, Table))
rerun test, and what do u know, it works
Firstly, it looks like you have implicit conversions turned on. That is the start of the issue. Secondly, you define Furniture as a List(of Object). Your first call to WhatIs is succeeding. The compiler is making a best guess as to which overload to use when passing what it sees as simply Object as it iterates through thing.Furniture, and it determines the public static version of the WhatIs method to be the most appropriate. It then attempts to implicitly convert Object to House, and inevitably fails.
Why does casting work? Because it takes the guess work out of determining which overload to use.
Moral of the story is: Don't make the compiler guess. Implicit conversion can lead to tricky bugs.
Edit: Why doesn't the compiler see the other overloaded functions?
The compiler has to determine the correct overload to use at compile time. It does not wait until runtime to determine which overload to use, and therefore doesn't have the luxury of inspecting the type of the object to determine the most appropriate overload.
Since the compiler only knows that furniture is a List(Of Object), technically (with implicit conversion turned on) all three of the overloads are deemed "appropriate," but the compiler must choose one. It ranks the possible overload candidates, and chooses the public version ahead of the private ones.
Use always
Option Strict On
You cannot make it more flexible by adding Methods equal in name, just with different parametertypes.
Update
Private Function ShowMe(data As Integer) As String
Return data.ToString
End Function
Private Function ShowMe(data As String) As String
Return data
End Function
Private Function ShowMe(data As Double) As String
Return data.ToString
End Function
Dim bla As New List(Of Object)
if you then call
bla.Add(12)
bla.Add("hi")
bla.Add(1.2)
Dim text As String
text = ShowMe(bla(0))
text = ShowMe(bla(1))
text = ShowMe(bla(2))
then the compiler will always complain that the correct method does not exist, because the correct method is not selected by checking the type, instead it is selected by the definition, for which type the container is defined for.
Private Function ShowMe(data As Object) As String
Return data.ToString
End Function
this would be called for all integer, doubles and strings. If it is not available, then some methods are used that can do some kind of automatic conversion. Thats why you can put an integer in a float, or put a number in a string.
One way would be to check for its type and do an explizit type conversion
For Each ele As Object In bla
If TypeOf ele Is Integer Then
text = ShowMe(CInt(ele))
ElseIf TypeOf ele Is Double Then
text = ShowMe(CDbl(ele))
Else
text = ShowMe(CStr(ele))
End If
Next
But this is still not so clean. If you want to access properties that all objects should support, then put them in a container and define the type as something that assures that those properties exist.