Assigning Values to properties in Structures - vb.net

In the below code "f" is an instance of the Class FORM which has a property "s" of type SIZE, a structure which has been defined in the code. My question is: When I try to assign values to the attributes of property "s" of the instance "t" directly it does not work: That is the statement f.s.height = 15 does not work. My confusion is arising from the fact that when I print the values of the property "s" of the instance "f", I am able to print the individual attributes of the structure SIZE but the same cannot be done while assignment of value. Assignment of values require me to call the constructor. Why is it so? What is preventing the assignment of the value to the attributes of "s": i.e. f.s.height & f.s.width?
Module Module1
Sub Main()
Dim f As New MyForm()
f.s = New Size(2, 5) 'Works Fine
f.colour = "Red" 'Assignment works just fine
'Below: Individual elements cannot be acceessed for assignment. WHY?
f.s.height = 15 'Doesn't Work
f.s.height = +2 'Doesn't work
'Individual elements can be accessed while printing
Console.WriteLine("Widht = {0}", f.s.width)
Console.WriteLine("Height = {0}", f.s.height)
Console.ReadLine()
End Sub
End Module
Class MyForm
Public Property s As Size
Public Property colour As String
End Class
Public Structure Size
Dim height As Integer
Dim width As Integer
Public Sub New(ByVal w As Integer, ByVal h As Integer)
width = w
height = h
End Sub
End Structure
Pls help.

The compiler should be indicating "Expression is a value and therefore cannot be the target of an assignment".
Change Size from a Structure to a Class (and Dim to Property) to fix the problem:
Public Class Size
Property height As Integer
Property width As Integer
Public Sub New(w As Integer, h As Integer)
width = w
height = h
End Sub
End Class
By the way, you'll also see this behaviour with the standard System.Drawing.Size which is defined as a Structure rather than a Class. (So is Point and probably others.)

This behavior is fundamental to value types (Structures). Conceptually, an instance of a value type is supposed to represent a single immutable value, and any instances with the same value are all supposed to be equivalent. As you have observed, you can get very surprising behavior if you try to change parts of an existing value type. It's really not intended for you to be able to alter them piecewise.
For this reason, I will always recommend that the members of a value type should be marked as ReadOnly so that you get blocked from trying to change them after construction.
If you want to be able to treat something like a mutable object instance instead of an immutable value, it needs to be a reference type (a Class). That's what they're designed to do.

After a lot of searching I came across the following article which probably explains the reason why we are not able to directly access the height/width attribute of the SIZE structure through an instance of the FORM class. Requesting people to go through this as the author has given a lot of details:
http://www.albahari.com/valuevsreftypes.aspx
PLs feel free to share any difference in opinion.

Related

VBA: Class Module: Get and Let

I have no experience with custom classes and a really simple question, but I found it difficult to google this:
I've come across an example (source) for using custom classes.
Module 1
Sub clsRectAreaRun()
'This procedure instantiates an instance of a class, sets and calls class properties.
Dim a As Double
Dim b As Double
Dim areaRect As New clsRectArea
a = InputBox("Enter Length of rectangle")
b = InputBox("Enter Width of rectangle")
areaRect.Length = a
areaRect.Width = b
MsgBox areaRect.rArea
End Sub
class module 'clsRectArea'
'Example - Create Read-Only Class Property with only the PropertyGet_EndProperty block.
Private rectL As Double
Private rectW As Double
Public Property Let Length(l As Double)
rectL = l
End Property
Public Property Get Length() As Double
Length = rectL
End Property
Public Property Let Width(w As Double)
rectW = w
End Property
Public Property Get Width() As Double
Width = rectW
End Property
Public Property Get rArea() As Double
'Read-Only property with only the PropertyGet_EndProperty block and no PropertyLet_EndProperty (or PropertySet_EndProperty) block.
rArea = Length * Width
End Property
My question is regarding this part of the code:
areaRect.Length = a
areaRect.Width = b
MsgBox areaRect.rArea 'rArea = Length * Width
From what I've read, that Get and Let properties have the same name is kind of the point. But I have to ask, how does the code know if it's supposed to call Get or Let? Is it simply down to if, in this case, Length and Width are to the left or to the right of the equal sign? As in, when you want to assign a value to the property, it automatically recognizes it's Let and if it's on the right, like for rArea here, the code is supposed to retrieve the value, so it's Get?
I know, extremely basic, but I'm not 100% sure and I simply want to know if I'm not messing up the something basic.
You can convince yourself which property method is being called by adding MsgBox's to the code in the class module.
For example:
Public Property Let Length(l As Double)
rectL = l
MsgBox "Let Length called."
End Property

In a generic VB.NET structure, how do I access its explicitly provided constructor?

First of all, what I want to achieve:
I want to extend a value datatype by providing additional properties, especially to validate ranges provided at declaration time. I want the new datatype to be a value type as well.
Compare with Ada:
subtype Day_Number is Integer range 1 .. 31;
Ideal, but obviously not implementable, would be:
Dim DayNumber As Int64 Range 1 To 31
However, I would be happy with:
Dim DayNumber As RangeInt64(1, 31)
It is of no concern, if initialization takes its time. Once ranges are provided, they are considered to be immutable. The datatype from then on is only used to set/get values like with ordinary value types, only that they are subject of being validated against the initially provided range.
My attempt:
Since I cannot inherit from structures in order to expand on them, I tried to incorporate a structure into a structure as a member.
In a module, I have this structure:
Friend Structure SRangeValueType(Of T)
Private lMinimum As T
Private lMaximum As T
Friend Property Minimum As T
Get
Return lMinimum
End Get
Set(tValue As T)
lMinimum = tValue
End Set
End Property
Friend Property Maximum As T
Get
Return lMaximum
End Get
Set(tValue As T)
lMaximum = tValue
End Set
End Property
Friend Sub New(Minimum As T, Maximum As T)
lMinimum = Minimum
lMaximum = Maximum
End Sub
End Structure
I attempt to use this generic structure as a member of another structure (of concrete type Int64):
Public Structure RangeInt64
Private Range As SRangeValueType(Of Int64)
End Structure
However, this is not using the constructor with the two arguments.
Say I want to initialize Range (the only member of the structure RangeInt64) with the values 100 and 200 for Minimum and Maximum, resp.
I am not allowed to use something like:
Private Range As SRangeValueType(Of Int64)(100,200)
What is the correct syntax to provide my values to the generic constructor?
Normally, if you add a constructor to a structure, you can call it using the New keyword:
Dim x As SRangeValueType(Of Int64) ' Calls the default, infered, parameter-less constructor
Dim y As New SRangeValueType(Of Int64)(100, 200) ' Calls the explicitly defined constructor
However, that's not really the problem. The problem is that you are trying to set the default value of a non-shared field in a structure. That is something which is never allowed. All non-shared fields in structures must default to their default value (i.e. Nothing). For instance:
Public Structure RangeInt64
Private x As Integer = 5 ' Error: Initializers on structure members are valid only for 'Shared' members and constants
Private y As New StringBuilder() ' Error: Non-shared members in a structure cannot be declared 'New'
End Structure
And, as you may already know, you cannot override the default, inferred, parameter-less constructor on a structure either:
Public Structure RangeInt64
Public Sub New() ' Error: Structures cannot declare a non-shared 'Sub New' with no parameters
x = 5
y = New StringBuilder()
End Sub
Private x As Integer
Private y As StringBuilder
End Structure
As such, you are stuck. By design, when the default constructor is used, all fields in the structure must always default to Nothing. However, if you really, really need it to be a structure, and you can't just convert it to a class, and you really need to change it's default value, you could theoretically fake it into working by using a property to wrap the field:
Public Structure RangeInt64
Private _Range As SRangeValueType(Of Int64)
Private _RangeInitialized As Boolean
Private Property Range As SRangeValueType(Of Int64)
Get
If Not _RangeInitialized Then
_Range = New SRangeValueType(Of Int64)(100, 200)
_RangeInitialized = True
End If
Return _Range
End Get
Set(value As SRangeValueType(Of Int64))
_Range = value
End Set
End Property
End Structure
It should go without saying, though, that it's pretty gross and should be avoided if possible.
Update
Now that you've provided more details about what you are trying to accomplish, I think I may have a better solution for you. You're right that structures do not support inheritance, but what they do support is interfaces. So, if all you need is a bunch of range types, one per value-type, all having the same minimum and maximum properties, but all returning different predetermined values, then you could do something like this:
Private Interface IRangeValueType(Of T)
ReadOnly Property Minimum As T
ReadOnly Property Maximum As T
End Interface
Private Structure RangeInt64
Implements IRangeValueType(Of Int64)
Public ReadOnly Property Minimum As Long Implements IRangeValueType(Of Int64).Minimum
Get
Return 100
End Get
End Property
Public ReadOnly Property Maximum As Long Implements IRangeValueType(Of Int64).Maximum
Get
Return 200
End Get
End Property
End Structure

Can two VBA Class properties write to each other to simulate overloading?

Say I have a class called Vc that represents a geometric vector AB.
We can define a Vector either by two points (xA, yA, xB, yB) or by a point, a distance and an angle (xA, yA, Len, Angle).
I would like to write the class Vc such that its properties can both be optional (to accommodate the above two methods of definition) and be performing automatically the calculation needed to "fill the missing optional
data": that is if the user writes to the properties xA, yA, xB, yB, then
the missing properties Len and Angle be automatically calculated by the code,
and vice versa.
Now to simply the coding, I will treat a similar problem instead of the
geometric problem:
suppose i have two numbers NbA and NbB, with NbA = 2 NbB, and the user can enter either NbA or NbB,
I tried this code, hoping that at least NbB be calculated by NbA
(or entered by the user)
Private pNbA As Double
Private pNbB As Double
Public Property Get NbA() As Double
NbA = pNbA
End Property
Public Property Let NbA(value As Double)
pNbA = value
End Property
Public Property Get NbB() As Double
NbB = pNbB
End Property
Public Property Let NbB(NbA As Double)
pNbB = NbA * 2
End Property
But when testing I got NbB = 0 (while nbA=1):
Sub TestAbove()
Dim aTest As TestClass
Set aTest = New TestClass
aTest.nbA = 1
MsgBox aTest.nbB 'this gives 0
End Sub
I hope that i can solve this issue within the properties
part itself of the class, without resorting to writing a lot
of methods that would introduce duplication of definitions,
I am afraid.
In VB.NET you would use a Nullable(Of Double).
In VBA for the same purpose you can use Variant/Empty:
Private pNbA As Variant ' Empty by default
Public Property Get NbA() As Double
if IsEmpty(pNbA) then NbA = 2 * NbB else NbA = pNbA
End Property
Public Property Let NbA(value As Double)
pNbA = value
End Property
However I believe it would be better if you defined class properties in a way that they only return stored values and do not depend on each other, and create several constructor functions in a module that would accept your different sets of arguments and initialize all fields of the class properly as if all properties were supplied.

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.

Set a type in VBA to nothing?

I have defined a variable with an own type, say
Dim point As DataPoint
Public Type DataPoint
list as Collection
name as String
number as Integer
End Type
and I want to delete all values of the variable point at once. If it was a class, I would just use Set point = New DataPoint, or set Set point = Nothing, but how can I proceed if it's a type?
You can benefit from the fact that functions in VB have an implicit variable that holds the result, and that contains the default type value by default.
public function GetBlankPoint() as DataPoint
end function
Usage:
point = GetBlankPoint()
The standard way is to reset each member to its default value individually. This is one limitation of user-defined types compared to objects.
At the risk of stating the obvious:
With point
Set .list = Nothing
.name = ""
.number = 0
End With
Alternatively, you can create a "blank" variable and assign it to your variable each time you want to "clear" it.
Dim point As DataPoint
Dim blank As DataPoint
With point
Set .list = New Collection
.list.Add "carrots"
.name = "joe"
.number = 12
End With
point = blank
' point members are now reset to default values
EDIT: Damn! Beaten by JFC :D
Here is an alternative to achieve that in 1 line ;)
Dim point As DataPoint
Dim emptyPoint As DataPoint
Public Type DataPoint
list As Collection
name As String
number As Integer
End Type
Sub Sample()
'~~> Fill the point
Debug.Print ">"; point.name
Debug.Print ">"; point.number
point.name = "a"
point.number = 25
Debug.Print ">>"; point.name
Debug.Print ">>"; point.number
'~~> Empty the point
point = emptyPoint
Debug.Print ">>>"; point.name
Debug.Print ">>>"; point.number
End Sub
SNAPSHOT
One-liner:
Function resetDataPoint() As DataPoint: End Function
Usage:
point = resetDataPoint()
Another option is to use the reserved word "Empty" such as:
.number= Empty
The only issue is that you will need to change the number from integer to variant.
Using classes in VBA is usually a good practice in case it is not a single purpose solution or the class do not contain too many private attributes because if you want to adhere on OOP rules and keep your class safe, you should declare all the Let and Get properties for all private attributes of class. This is too much coding in case you have more than 50 private attributes. Another negative side of using classes in excel is fact, that VBA do not fully support the OOP. There is no polymorfism, overloading, etc.) Even you want to use an inheritance, you have to declare all the attributes and methods from the original class in the inherited class.
So in this case I would prefer the solution suggested by Jean-François Corbett or GSeng, i.e. to assign an empty variable of the same UDT as the variable you want to clear or to use a function which to me seems little bit more elegant solution because it will not reserve permanent memory for the emtpy variable of your UDT type.
For that is better to use classes, you can declare a class module with the name of your type, then declare all of your members as public, then automatically you can set to nothing and new for create and delete instances.
syntax will be somthing like this after you create the class module and named like your type:
'
Public List as Collection
Public Name as String
Public Number as Long
Private Sub Class_Initialize()
'Here you can assign default values for the public members that you created if you want
End Sub