How to create a list of multiple object types and preserve methods/properties? - vb.net

I have a program that de-serializes stuff from an xml file and does all kinds of fancy things with it. I have 2 arrays in the XML file, one called variables and one called lookupTables. I also have 2 classes, variable and lookupTable. Both of those classes inherit from a class called definition. definition is inherit only and has one method that must be inherited, evaluate. Here is the code:
Definition
Public MustInherit Class Definition
Public Sub New()
End Sub
<XmlAttribute("name")> _
Public Property name As String
Public MustOverride Function evaluate(variables As Dictionary(Of String, Double)) As Double
End Class
Variable
<XmlRoot("variable")> _
Public Class Variable
Inherits Definition
<XmlAttribute("value")> _
Public Property value As String
Public Overrides Function evaluate(variables As Dictionary(Of String, Double)) As Double
Dim calculator As CalculationEngine = New CalculationEngine()
Return calculator.Calculate(value, variables)
End Function
End Class
LookupTable
<XmlRoot("lookupTable")> _
Public Class LookupTable
Inherits Definition
Public Sub New()
End Sub
<XmlElement("data")> _
Public Property data As Integer()()
Public Overrides Function evaluate(variables As Dictionary(Of String, Double)) As Double
Return True
End Function
End Class
My question is (hopefully) very simple. How can I create a list of Defintions (so a list containing both Variables and LookupTables) without loosing their individual methods and properties. All I will need to use this list for is calling evaluate.
I thought I could just create a List(Of Definition) since both Variable and LookupTable are guaranteed to implement evaluate() but as I read, it seems that typecasting both of the lists would strip them of their own innards and keep onluy what is common with Definition. What can I do here?

Since both your objects inherit from definition, you could create a list of Definition items then when you need to access specific methods, you cast them to their proper type using directCast to their specific type. To determine the type, you can use
If you had multiple variables types not inheriting from the same base, you could create a list of objects and apply the same idea.
'List of definition item
Private _List As New List(Of Definition)
'When you want to use specific method, you can cast items back to their types.
For Each Item As Definition In _List
Select Case Item.GetType
Case GetType(LookupTables)
Dim Table As LookupTables = DirectCast(Item, LookupTables)
Table.msg() 'Method in my LookupTables class only.
Case GetType(Variables)
Dim Variable As Variables = DirectCast(Item, Variables)
Variable.WriteToConsole() 'Method found in Variables class only.
End Select
Next
As for casting,
you can cast your LookupType to definition and vice-versa to use their respective methods as needed.

The simple answer was to use an ArrayList.

Related

VBA List of Custom Datastructures

One of the main problems in VBA are custom data structures and lists.
I have a loop which generates with each iteration multiple values.
So as an example:
Each loop iteration generates a string "name" an integer "price" and an integer "value".
In C# for example I'd create a class which can hold these three values and with each loop iteration I add the class object to a list.
How can I do the same thing in VBA if I want to store multiple sets of data when not knowing how many iterations the loop will have (I cant create an array with a fixed size)
Any ideas?
The approach I use very frequently is to use a class and a collection. I also tend to use an interface model to make things more flexible. An example would look something like this:
Class Module IFoo
Option Explicit
Public Sub Create(ByVal Name as String, ByVal ID as String)
End Property
Public Property Get Name() as String
End Property
Public Property Get ID() as String
End Property
This enforces the pattern I want for my Foo class.
Class Module Foo
Option Explicit
Private Type TFoo
Name as String
ID as String
End Type
Private this as TFoo
Implements IFoo
Private Sub IFoo_Create(ByVal Name as String, ByVal ID as String)
this.Name = Name
this.ID = Name
End Sub
Private Property Get IFoo_Name() as String
IFoo_Name = this.Name
End Property
Private Property Get IFoo_ID() as String
IFoo_ID = this.ID
End Property
We get intellisense from the Private Type TFoo : Private this as TFoo where the former defines the properties of our container, the latter exposes them privately. The Implements IFoo allows us to selectively expose properties. This also allows you to iterate a Collection using an IFoo instead of a Foo. Sounds pointless until you have an Employee and a Manager where IFoo_BaseRate changes depending on employee type.
Then in practice, we have something like this:
Code Module Bar
Public Sub CollectFoo()
Dim AllTheFoos as Collection
Set AllTheFoos = New Collection
While SomeCondition
Dim Foo as IFoo
Set Foo = New Foo
Foo.Create(Name, ID)
AllTheFoos.Add Foo
Loop
For each Foo in AllTheFoos
Debug.Print Foo.Name, Foo.ID
Next
End Sub
While the pattern is super simple once you learn it, you'll find that it is incredibly powerful and scalable if implemented properly. It also can dramatically reduce the amount of copypasta that exists within your code (and thus reduce debug time).
You can use classes in VBA as well as in C#: Class Module Step by Step or A Quick Guide to the VBA Class Module
And to to the problem with the array: you can create an array with dynamic size like this
'Method 1 : Using Dim
Dim arr1() 'Without Size
'somewhere later -> increase a size to 1
redim arr1(UBound(arr1) + 1)
You could create a class - but if all you want to do is hold three bits of data together, I would define a Type structure. It needs to be defines at the top of an ordinary module, after option explicit and before any subs
Type MyType
Name As String
Price As Integer
Value As Integer
End Type
And then to use it
Sub test()
Dim t As MyType
t.Name = "fred"
t.Price = 12
t.Value = 3
End Sub

So a VB interface can't have shared functions. Is there an alternative to creating dummy objects?

To avoid getting into the weeds on my particular program, let me just create a simplified case.
I have a generic class that should work on a variety of objects. Each of those objects must implement a certain interface.
What I WANT to say is something like:
Public Interface GenThing
Shared Function thing_name() As String ' This doesn't work! Can't be shared!
Sub FillOne(row As DataRow)
End Interface
public class Thing1
implements GenThing
public shared function thing_name() as string implements GenThing.thing_name
return "thing number one"
end function
public sub FillOne(row as DataRow) implements GenThing.MakeOne
... bunch of work ...
end sub
end class
public class ThingUtil(of T as {GenThing,New})
public function GetList(id as integer) as List(of T)
dim name=T.thing_name() ' This doesn't work!
dim ds as DataSet=GetData(name,id) ' bunch of work here that's the whole point of the class but not relevant to the question
dim my_list = new List(of T)
for each row as DataRow in ds.tables(0).rows
dim my_t = new T()
my_t.FillOne(row)
my_list.add(my_t)
next
return my_list
end function
end class
Do you get my problem? I need every class that implements the interface to have a function that returns a "name" that is used to get the data that is needed to create an instance of the object. But I need to know this name BEFORE I create the instance, because I need it to be able to create the instance. But VB doesn't allow an interface to have a shared function, so what I want to write doesn't work.
So what I've done is this:
I make thing_name not shared.
Then instead of simply "dim name=T.thing_name()", I write
dim dummy = new T()
dim name = dummy.thing_name()
Okay, it works, but it seems really ugly. I create an instance of the object, with all the overhead that that involves, just to get a piece of constant text.
Is there a better way? Or am I making a big deal out of nothing?
Update
I see that two people voted to close this question on the grounds that it is the same as "Why can't we have shared functions in an interface?"
I am not asking why I can't have a shared. I am saying, GIVEN that I can't, how do I solve this particular problem?
There's no really simple way of fixing this, no.
Depending on what thing_name does, however, you might approach things in a different way. If each implementation just returns a constant value, then it's effectively metadata about the class - and could be described in an attribute instead, which can be fetched at execution time. (See Type.GetCustomAttributes.) Unfortunately you can't then enforce all types implementing the interface to be decorated with the attribute - but you could write a unit test to check this pretty easily.
If thing_name needs to really do work at execution time, that's tougher. You could potentially look for a well-known shared method name instead and execute that via reflection (and again have unit tests to check that it's implemented properly).
I realize this is from a few years ago, but running into a similar problem, I wanted to offer a different solution. Pass a delegate as parameter to the ThingUtil constructor. You avoid having to put a shared method in an interface, and the constructor will force you to include the parameter at compile time.
You can add more delegates if needed, or to make it even simpler in this case, just pass name as a string instead of get_name as a delegate.
Define the delegate in the interface:
Public Interface GenThing
Delegate Function ThingNameDelegate() As String
Sub FillOne(row As DataRow)
End Interface
Public Class Thing1
Implements GenThing
Public Shared Function thing_name() As String 'name this whatever you want
Return "thing number one"
End Function
Public Sub FillOne(row As DataRow) Implements GenThing.FillOne
'do stuff
End Sub
End Class
In ThingUtil, add a member to store the delegate, a constructor parameter to to accept, and call it with .Invoke():
Public Class ThingUtil(Of T As {GenThing, New})
Private m_thing_name As GenThing.ThingNameDelegate
Public Sub New(thing_name As GenThing.ThingNameDelegate)
m_thing_name = thing_name
End Sub
Public Function GetList(id As Integer) As List(Of T)
Dim name = m_thing_name.Invoke()
Dim ds As DataSet = GetData(name, id) ' bunch of work here that's the whole point of the class but not relevant to the question
Dim my_list = New List(Of T)
For Each row As DataRow In ds.Tables(0).Rows
Dim my_t = New T()
my_t.FillOne(row)
my_list.Add(my_t)
Next
Return my_list
End Function
End Class
Finally, use it like this:
Dim tu as new ThingUtil(Of Thing1)(AddressOf Thing1.get_name)
tu.GetList(1)

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.

Is it possible to add an interface to an existing type?

I've developed some serialization code for my types. They all share a common interface to facilitate the serialization logic. Would it be possible to also add that interface to some basic types like Integer or String so I could pass one of those basic type values into my serialization logic and have it work? I'm imagining something along the lines of extension methods, but adding an interface rather than a method? I'm sure I could come up with some way to do it using late-binding, but I'd like to avoid that, if possible.
No. It is not possible to extend an existing type to make it implement an interface (short of adding Implements IMyInterface to the top of the code for that type, that is). The closest thing to that would be to create a derived class which adds the interface to the base class. If you override the CType operator you could even make it so values could be seamlessly converted from one type to the other without explicitly casting them. However, since you mentioned String and Integer as the types that you want to extend, that is not even possible. You can't create a new type that inherits from String because String is defined as NotInheritable. Similarly, you can't create a new type that inherits from Integer because Integer is a Structure, not a Class. Structures do not support inheritance.
Therefore, the best option that you have would be to create a new class which wraps the core value, extends it by implementing the interface, and then overrides the CType operator to make it simple to convert between the core type and the wrapper type. For instance, let's say you had an interface like this:
Public Interface IWritable
Sub Write()
End Interface
And you had a method that took an argument of that type, like this:
Private Sub TestWrite(writableObject As IWritable)
writableObject.Write()
End Sub
If you needed to pass an Integer into that method, you could make a wrapper class like this:
Public Class WritableInteger
Implements IWritable
Public Sub New(value As Integer)
Me.Value = value
End Sub
Public Property Value As Integer
Public Sub Write() Implements IWritable.Write
Console.Write(Value)
End Sub
Overloads Shared Widening Operator CType(value As Integer) As WritableInteger
Return New WritableInteger(value)
End Operator
Overloads Shared Widening Operator CType(value As WritableInteger) As Integer
Return value.Value
End Operator
End Class
Since the CType operator is overloaded as Widening, that means that you can convert the value between the two types without casting (even with Option Strict On). For instance, this works:
Dim w As WritableInteger = New WritableInteger(5)
Dim i As Integer = w
w = i
Unfortunately, since TestWrite is asking for an IWritable rather than a WritableInteger, you can't just call TestWrite with an Integer, like this:
Dim i As Integer = 5
TestWrite(5) 'This won't work!
The compiler knows that it needs to convert the Integer to an IWritable object, but since any number of types may implement that interface, it doesn't automatically try to figure out if any of them provide a CType operator for that. Since there may be multiple types that allow widening conversions from Integer to IWritable, it just throws up its hands and cries fowl. Therefore, even though the widening conversion is declared, you still have to explicitly cast the type in a case like that. For instance:
Dim i As Integer = 5
TestWrite(CType(i, WritableInteger))
Or, perhaps more simply:
Dim i As Integer = 5
TestWrite(New WritableInteger(5))
You could make it more convenient by creating overloads for all the common types that will need to be wrapped. For instance, if you created an overload to the TestWrite method, like this:
Public Sub TestWrite(value As Integer)
TestWrite(New WritableInteger(value))
End Sub
Then you could easily call it like this:
TestWrite(5)
Converting back from an IWritable variable to an Integer, though, is even more difficult. For instance:
Dim w As IWritable = New WritableInteger(5)
Dim i As Integer = w ' This won't work!
Dim i2 As Integer = CType(w, Integer) ' Whis won't work either!
If you need to do that, you'd actually have to first cast it to a WritableInteger (and know that it is that type of object in the first place), for instance:
Dim w As IWritable = New WritableInteger(5)
If TypeOf w Is WritableInteger Then
Dim i As Integer = CType(w, WritableInteger)
End If
Unfortunately, there's really no way to make that any easier while still maintaining the safety of the compile-time type checking.
Finally it's also worth mentioning that, if you decide to make a wrapper like that, and the implementation of the interface is the same regardless of the wrapped type, then you could implement it as a generic type, like this:
Public Class Writable(Of T)
Implements IWritable
Public Sub New(value As T)
Me.Value = value
End Sub
Public Property Value As T
Public Sub Write() Implements IWritable.Write
Console.Write(Value)
End Sub
Overloads Shared Widening Operator CType(value As T) As Writable(Of T)
Return New Writable(Of T)(value)
End Operator
Overloads Shared Widening Operator CType(value As Writable(Of T)) As T
Return value.Value
End Operator
End Class
Then you could call the TestWrite method like this:
TestWrite(New Writable(Of Integer)(5))
TestWrite(New Writable(Of String)("Hello World"))

How can I get a property name for a type without the need to instantiate an object of that type?

I have a requirement where I need to have a "type safe" way of accessing property names, without actually instantiating an object to get to the property. To give an example, consider a method that takes as arguments a list of IMyObject and a string that represents a property name (a property that exists in IMyObject).
The methods implementation will take the list and access all the objects in the list using the property name passed... for some reason or another, we won't dwell on that!!
Now, I know that you can do this using an instantiated object, something like ...
Dim x as MyObject = nothing
Dim prop As PropertyInfo = PropHelper.GetProperty(Of MyObject)(Function() x.MyProperty)
Where my helper method uses reflection to get the name of the property as a string - there are numerous examples of this flying around on the web!
But I don't want to have to create this pointless object, I just want to do something like MyObject.MyProperty! Reflection allows you to iterate through a types properties and methods without declaring an object of that type... but I want to access a specific property and retrieve the string version of its name without iteration and without declaring an object of that type!
The main point here is that although I am trying to get the property name as a string... this is done at run time... at compile time, I want this to be type safe so if someone changes the property name, the compilation will break.
Can anyone help in this quest!?!
So here is a quick code-listing to demonstrate the answer that I was looking for:
Imports System.Linq.Expressions
Public Class A
Public Prop1 As String
Public Prop2 As Integer
End Class
Public Class Form1
Public Function GetPropertyNameB(Of TModel, TProperty)(ByVal [property] As Expression(Of Func(Of TModel, TProperty))) As String
Dim memberExpression As MemberExpression = DirectCast([property].Body, MemberExpression)
Return memberExpression.Member.Name
End Function
Public Sub New()
InitializeComponent()
Dim propertyName As String = GetPropertyNameB(Function(myObj As A) myObj.Prop1)
Dim propertyName2 As String = GetPropertyNameB(Function(myObj As A) myObj.Prop2)
MsgBox(propertyName & " | " & propertyName2)
End
End Sub
End Class
You may be able to pass the property in as a simple lamdba expression, and take it in the method as an expression tree. You should be able to analyze the expression tree to get the string name of the property, but it the lambda expression will fail to compile if the property name changes. Check out this page for more details:
http://msdn.microsoft.com/en-us/library/bb397951.aspx
You can make use of the NameOf function:
Dim fieldName = nameOf(MyClass.MyField)