How do I redefine the class of a variable programmatically? - arrays

Ok, so I have 2 classes, Note and HeldNote. HeldNote inherits from Note and adds only 2 variables, plus a different way of setting visual dimensions. I have an array of an undefined amount of items, with each item defined as a member of class Note.
Occasionally during my program, I want one of these items in the array to be a HeldNote rather than a Note, and I do not know which one until the program is running.
VB.Net seems fine with me declaring array(i) = New HeldNote but then wont let me access the variables that HeldNote has but Note does not. Using Breaks in the program leads me to believe that array(i) is not actually being redefined as a HeldNote rather than a Note.
So, my question is, how can I make array(i) a HeldNote, rather than a Note?
I'm using VB.net 2008.
Edit: I asked my Teacher about this problem and he said he didn't know and told me to look it up. I couldn't anything online (had been looking since before i asked him) so I've come here for help.

The Array of Note (better use a List(Of Note) if you are unsure about the size) can store both Note and HeldNote because the latter derives from the first.
But, when you store an HeldNote in the array it is not automatically transformed in a Note, it is still an HeldNote, what you need is to try casting it back to an HeldNote
For example
Sub Main
Dim X(2) As Note
X(0) = New Note() WIth {.Note = "A Message"}
X(1) = New HeldNote() With {.Note = "Held Message", .Author = "Steve"}
Dim h As HeldNote
h = TryCast(X(0), HeldNote)
If h Is Nothing Then
Console.WriteLine("X(0) is NOT an HeldNote")
Else
Console.WriteLine(h.Author)
End If
h = TryCast(X(1), HeldNote)
If h Is Nothing Then
Console.WriteLine("X(1) is NOT an HeldNote")
Else
Console.WriteLine(h.Author)
End If
End Sub
Public Class Note
Public Note As String
End Class
Public Class HeldNote
Inherits Note
Public Author As String
End Class
In thisway you still have your HeldNote without using an array of Object that is something to avoid because you loose all the strong typing allowed by a specific Note array (In an Object array you could store anything. A Button or a String makes no difference)

A few suggestions:
1) Change your design and have just one class rather than one that inherits from another.
2) Declare your array as type Object. Each element of an Object array can be of a different type. This way you can assign each element's type at runtime.
Dim notearray As Object() = New Object() {New Note(), New HeldNote(), New HeldNote(), New Note()}

This is not a code issue, it's almost a OO question.
"I have an array of an undefined amount of items, with each item defined as a member of class Note."
Then all of them are "Note". You can create class instances so:
Note theNote = new HeldNote()
but you can't "downgrade" a instance on execution declared so:
Note theNote = new Note()".
If you need "randomly" a type change, you can create a ListOf(HeldNote).
Then, you can decide the real type so:
Dim aList as new ListOf(HeldNote)
aList.add(new HeldNote())
aList.add(new HeldNote())
aList.add(new HeldNote())
Dim oNote as Note
oNote = Ctype(aList(0),Note)
Dim oNote as HeldNote
oNote = aList(1)
...

Related

VBA Array Public Method cant be modified by components

I'm working with a rather complex problem in VBA and I decided to structure it in different classes.
One of the classes containd an array and I need to modify the component individualy outside the class.
The problem can de reduced to the definition of the class
Public a As Variant
Private Sub Class_Initialize()
ReDim a(5) As Double
' Set some value
a(1) = 20
End Sub
And a sub to access the data:
Sub Test()
Dim b As New DummyTest
b.a(4) = 7
Debug.Print (b.a(1))
Debug.Print (b.a(4))
End Sub
The result on the debug window is 20 0. I've checked this result with an inspection and confirm that the property a cannot be modified from the exterior of the class.
Funny part is that the property can be modified if replaced by other array, for example:
Sub Test2()
Dim b As New DummyTest
b.a = Array(1, 2, 3, 4, 5)
Debug.Print (b.a(1))
Debug.Print (b.a(4))
End Sub
Behaves as expected. I'm confused as element access is allowed inside the class context and in any other situation, but in this specific case it doesn't wotk. There is no error msg, it simply refuses to change the content of the array.
I'll find work around solutions but missing this way to access data is being a really PITA.
Hope you can help me with this issue.
Thank you in advance!
Your "property" is breaking encapsulation, which pretty much defeats its entire purpose.
Public a As Variant
From a public interface standpoint, this is a read-write property (if you added a new class module with Implements DummyTest you'd have to add Property Get and Property Let members for it). From the class' standpoint, it's a public instance field.
When a class encapsulates an array, a collection, a dictionary, or any other data structure, the last thing you want is for that data structure to be publicly exposed, with anyone anywhere being able to just overwrite the class' entire internal state as it pleases - you're not getting any of the advantages of classes that way.
First step to proper encapsulation is to make the instance field Private. Next step is to expose actual Property accessors. You want the array data to be writable? Expose an indexed Property Let member for it.
Private a As Variant
Private Sub Class_Initialize()
ReDim a(0 To 10)
End Sub
Public Property Get Value(ByVal index As Long) As Variant
Value = a(index)
End Property
Public Property Let Value(ByVal index As Long, ByVal newvalue As Variant)
a(index) = newvalue
End Property
This code behaves exactly as advertised:
Public Sub Test()
Dim b As DummyTest
Set b = New DummyTest
b.Value(4) = 7
Debug.Print b.Value(1)
Debug.Print b.Value(4)
End Sub
Note that because the array is encapsulated, that client code is absolutely unable to re-assign the array itself: the array is abstracted away and only accessible through the means exposed by the class.
Reference variable in VBA must be declared, so that we know what type of object we are referring to
i.e. Dim b As DummyTest ' No new required in this step
Before using a declared reference to store information create an instance of the Class.
i.e. Set b = New DummyTest
This might seem confusing but is actually very logical. Because b is a Class the Dim statement sets up b to hold the address of a DummyTest object. At the time of the Dim statement no object has been created (no memory allocated) so the address of b is nothing (no value or null). The set statement tells VBA to create an instance of your DummyTest class (allocate the memory it needs) and puts the address of the instance (memory) into variable b. Then because VBA knows that b points to an instance of a class you never see the address of b when you use it, just the data to which b is pointing. You can get the the address b is holding using the function Objptr but this is only something you would do in very special/specific circumstances

Is it possible to create an Array of Collections in VB.net or is it bad practice? What other replacements with the same function exist?

I am creating an app that has to create controls with specific parameters (some of which are custom parameters used for other applications) so the original method was like this
PSEDO CODE(kinda)
Dim ControlType1Color() as color
Dim ControlType1Name() as string
Dim ControlType1Parameter() as string
...
Dim ControlType2Color() as color
...
However when implementing that notation it ends up causing me to do quite a few test statements in order to recognize which type of control it is, Then find the correct variable to use. Which is honestly a waste of code space which could be saved.
While I was researching for something similar to java Objects (i believe?) I came across collections which should be what I need. However I need to know how I can implement it for this? Does a collection have infinite length? Can you use it as an array? And can you nest it with an array so you have an Array of Collections or is that just unnecessary?
EDIT FOR CLARIFICATION:
I am trying to record my controls that I created in run-time basically and try to use a single variable to record all Data
Lets say I have two textboxes and one button
My "Array of Collections" or whatever it would be would be like
AoC(0).Color = color
AoC(0).Type = Textbox
AoC(0).ID = ID
Aoc(1).Type = Textbox
...
Aoc(2).Type = Button
...
So if I needed to change anything I just change this thing only. I can handle the actual changes in code, I just need to know how to store it in the memory without using 5-15 variables needlessly.
You need to do something like this:
Private Structure ControlDetail
Public Color As System.Drawing.Color
Public ControlType As Type
Public ID As String
End Structure
Then you can define your data as:
Dim Aoc = New ControlDetail() _
{ _
New ControlDetail() With { .Color = System.Drawing.Color.Red, .ControlType = GetType(TextBox), .ID = "txtFoo1" }, _
New ControlDetail() With { .Color = System.Drawing.Color.Blue, .ControlType = GetType(Button), .ID = "butFoo2" } _
}
Then you can get access to the data as Aoc(0).ID, for example.

VB Array IndexOf error (Noughts and Crosses Game)

Being new to programming and having it introduced to me through my course I've been doing tasks in and out of College in Visual Basic using Visual Studio to make games and other little applications. However in my most recent project i've experienced a problem in one of my arrays I have never come across before. The specific error im getting comes up with this when highlighted:
Data type(s) of the type parameter(s) in method 'Public Shared Overloads Function IndexOf(Of T)(array As T(), value As T) As Integer' cannot be inferred from these arguments. Specifying the data type(s) explicitly might correct this error.
I have all the arrays (18, for 9 different buttons each containing a question and an answer variant) running in form load as it was the only way I could get the arrays to work with a randomiser and show the question in the button. Then the array index is being found and created in the submit answer button. I'll give some snippets for further context below from various points where the array is being referenced :)
I created this to make the string global but I have an inkling this is wrong?
Public Class Form3
Public QBox1 As String
Public QBoxA1 As String
This is where the arrays are and how i've structured them
Public Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BOX 1 (TOP LEFT)
Dim QBox1() As String = {"√81", "4x6", "16/4", "21+18", "81-23"}
Dim QBoxA1() As Integer = {"9", "24", "4", "39", "58"}
And finally this is within the submit answer button where the error is
If QBoxA1.Contains(txt_AnswerAttempt.Text) Then
Dim question_index = Array.IndexOf(QBox1, btn_Q1.Text)
Dim answer_index = Array.IndexOf(QBoxA1, answerAttemptDisplay.Text)
If question_index = answer_index Then
MsgBox("Correct Answer!")
Else
MsgBox("Wrong Answer!")
End If
End If
Sorry for information overload, I wanted to be thorough right off the bat! Cheers for giving this a read if you made it to the end xD
Your declaration isn't right. Since you want an array, don't declare it as a single string. It should be:
Public QBox1() As String
then in the load event, don't re-declare it with a Dim statement. Just re-populate it:
QBox1 = {"√81", "4x6", "16/4", "21+18", "81-23"}

Define List(Of T) at declaration

Is it possible to have a compound declaration of a List(Of T) wherein the size of the list is specified and the list is populated with the objects of type T with default values?
For example, when T is Integer we can do the following for short lists:
Public MyList As New List(Of Integer)(New Integer() {1, 2, 3})
But if I try the following for a longer, non-trivial object, list:
Public MyList As New List(Of MyClass)(New MyClass(100) {})
It seems the list is not populated with instances of MyClass and attempts to write to the items results in the exception:
"Object reference not set to an instance of an object"
Why should one work and not the other?
(I know I can add objects individually using the Add() method but there must be a neater way of setting up an array of specified size of default-valued empty objects [which is actually what I want], right?)
EDIT: In my use case MyClass is a simple data only (at the moment) class like
Public Class MyClass
Public Value As Byte
Public Shadow As Byte
End Class
Maybe using a struct instead might be easier FWIW?
Is it possible to have a compound declaration of a List(Of T) wherein the size of the list is specified and the list is populated with the objects of type T with default values?
Yes. But (at least in the 4.5.2 framework and VS2013's VB.Net1 version) the number of initialisers needs to match the capacity being set:
Sub Main()
Dim x = New List(Of Integer)(100) {1, 2, 3, 4}
Console.WriteLine(x(0))
End Sub
gives:
Array initializer is missing 97 elements.
However:
Public MyList As New List(Of MyClass)(New MyClass(100) {})
It seems the list is not populated with instances of MyClass and attempts to write to the items results in the exception:
You need to create the objects. The creation of MyList will have 100 elements but they will all be Nothing (null). You need to create the objects to use the members of MyClass:
MyList(0) = new MyClass();
MyList(0).Value = 42
Maybe using a struct instead might be easier FWIW?
Based on this question: no it will not be because that natural syntax (eg. the assignment to the Value member above) will not give you the results you expect. Structs are copied, so with structs that statement would only change a copy of the struct instance and not the instance in the collection.
1 I don't keep track of VB.Net's version numbers.

How to use Property Let for Arrays?

I am very new to VBS, but I am not able to implement even the simplest things, as it seems. I want to have a class which holds an array in a private member. Since I want to "inject" the array I tried to implement a "setter-method" using the Let functionality.
Class CPhase
Private m_AllowedTasks()
Public Property Let AllowedTasks(p_AllowedTasks)
m_AllowedTasks = p_AllowedTasks
End Property
Private Sub Class_Initialize()
ReDim m_AllowedTasks(0)
End Sub
End Class
This class is used as follows:
Dim allowed
allowed = Array("task1", "task2")
Dim phase
Set phase = New CPhase
phase.AllowedTasks = allowed
This results in a "Microsoft VBScript runtime error (...) : Type mismatch" in the Let-method. I also tried using different combinations of "ByVal", "ByRef", but since having absolutely no experience with VBS I couldn't find a solution. So what am I doing wrong?
Any hints or links to helpful ressources are very appreciated!
Thanks!
The culprit is
Private m_AllowedTasks()
which creates an abomination - a fixed array of no size. Just remove the ().
Private m_AllowedTasks
to create an (empty) Variant that may be set=let to an useful (redim-able) array.

Resources