Passing a derived class containing an overloaded property as a parameter - vb.net

I have two objects, one is a base class and the other is a derived class which inherits the base class. One of the properties on the derived class overloads a property on the base class. Now, I want to perform some calculations on both of these objects one by one by passing them as a parameter into a function. The problem is, if I define the parameter of this function as a base class, then when passing the derived class, the value of the overloaded property gets lost!
The reason I'm using a derived class is to temporarily add more properties/modify the existing properties of the base class to perform additional calculations, in order to reuse the base class.
I've tried 4 different functions, but none of them are any good. They either don't work correctly, or there is duplicate code, which I need to avoid because there will be a lot more code later. Below is the pseudo code.
Defining the classes:
Class BaseClass
Public Property Name As String
Public Property Value As Integer
End Class
Class DerivedClass
Inherits BaseClass
Overloads Property Value As Double
End Class
Initializing:
Dim MyBaseObject As New BaseClass()
MyBaseObject.Name = NameOf(MyBaseObject)
MyBaseObject.Value = 5
Dim MyDerivedObject As New DerivedClass
MyDerivedObject.Name = NameOf(MyDerivedObject)
MyDerivedObject.Value = 5.3
Calling the functions:
ProcessClass1(MyBaseObject)
ProcessClass1(MyDerivedObject)
ProcessClass2(MyBaseObject)
ProcessClass2(MyDerivedObject)
ProcessClass3(MyBaseObject)
ProcessClass3(MyDerivedObject)
ProcessClass4(MyBaseObject)
ProcessClass4(MyDerivedObject)
The functions:
Sub ProcessClass1(inClass As Object) 'functions correctly, but no intellisense
Console.WriteLine(inClass.Name & " " & inClass.Value)
End Sub
Sub ProcessClass2(inClass As BaseClass) 'does not function correctly, but has intellisense
Console.WriteLine(inClass.Name & " " & inClass.Value) 'Value displays 0 when passing MyDerivedObject, it should be 5.3!
End Sub
Sub ProcessClass3(inClass As Object) 'functions correctly, has intellisense, but need to write code for all possible derived types in advance
If inClass.GetType = GetType(BaseClass) Then
Dim inBaseClass As BaseClass = inClass
Console.WriteLine(inBaseClass.Name & " " & inBaseClass.Value)
End If
If inClass.GetType = GetType(DerivedClass) Then
Dim inDerivedClass As DerivedClass = inClass
Console.WriteLine(inDerivedClass.Name & " " & inDerivedClass.Value)
End If
End Sub
Sub ProcessClass4(inClass As BaseClass) 'method overloading: functions correctly, has intellisense, but need to write a duplicate method for every derived type
Console.WriteLine(inClass.Name & " " & inClass.Value)
End Sub
Sub ProcessClass4(inClass As DerivedClass) 'method overloading: functions correctly, has intellisense, but need to write a duplicate method for every derived type
Console.WriteLine(inClass.Name & " " & inClass.Value)
End Sub
Extra: Generics
I don't see any advantage with generics, the below snipped runs into the same problem as ProcessClass2:
Dim MyProcessGenericObject As New ProcessGenericClass(Of BaseClass)
MyProcessGenericObject.processNewItem(MyBaseObject)
MyProcessGenericObject.processNewItem(MyDerivedObject)
Public Class ProcessGenericClass(Of T As BaseClass)
Public Sub processNewItem(ByVal newItem As T)
Console.WriteLine(newItem.Name & " " & newItem.Value) 'Value displays 0 when passing MyDerivedObject!
End Sub
End Class
Of these 4 functions, ProcessClass1 is the most elegant with the least amount of code, but there is no intellisense on inClass which makes it impossible to maintain.
What I need is no duplication of code, intellisense, a method which can take derived classes inherited from the same base class, and without losing the data contained in any overloaded properties. What would be the best way to achieve this? Thanks.

What you have put forward will not work with the instance you pass around being the base class. That instance's value property will always be an integer unless you are able to cast the instance to the appropriate derived class (and that (double)int cast is where you have lost precision).
But a combination of some of these generics may help. Note, the base class will not hold an integer, rather an Object.
Public Class BaseClass
Public Property Name As String
Public Property Value As Object
End Class
Public Class BaseClass(Of T)
Inherits BaseClass
Public Overloads Property Value As T
Get
Return CType(MyBase.Value, T)
End Get
Set(value As T)
MyBase.Value = value
End Set
End Property
End Class
Class DerivedClassDouble
Inherits BaseClass(Of Double)
End Class
Class DerivedClassInteger
Inherits BaseClass(Of Integer)
End Class
The process method
Sub ProcessClass(inClass As BaseClass)
Console.WriteLine($"{inClass.Name} {inClass.Value}")
End Sub
Some options for instantiation
Dim [myBase] As New BaseClass()
[myBase].Name = NameOf([myBase])
[myBase].Value = 5
Dim myBaseInteger As New BaseClass(Of Integer)
myBaseInteger.Name = NameOf(myBaseInteger)
myBaseInteger.Value = 5
Dim myDerivedInteger As New DerivedClassInteger
myDerivedInteger.Name = NameOf(myDerivedInteger)
myDerivedInteger.Value = 5
Dim myBaseDouble As New BaseClass(Of Double)
myBaseDouble.Name = NameOf(myBaseDouble)
myBaseDouble.Value = 5.3
Dim myDerivedDouble As New DerivedClassDouble
myDerivedDouble.Name = NameOf(myDerivedDouble)
myDerivedDouble.Value = 5.3
ProcessClass([myBase])
ProcessClass(myBaseInteger)
ProcessClass(myDerivedInteger)
ProcessClass(myBaseDouble)
ProcessClass(myDerivedDouble)
Console.ReadLine()
myBase 5
myBaseInteger 5
myDerivedInteger 5
myBaseDouble 5.3
myDerivedDouble 5.3
I think the closest to your implementation would be to use [myBase] and myDerivedDouble instances. Then changing the generic base class to Public MustInherit Class BaseClass(Of T) would make the intent clearer.
Hopefully last edit, sorry for the long-winded answer.
You can just change your original classes to have an object in the base class, and use the property implementation I laid out, and that seems to get the job done without any generics. Again, it may or may not work in your exact implementation
Sub Main()
Dim MyBaseObject As New BaseClass()
MyBaseObject.Name = NameOf(MyBaseObject)
MyBaseObject.Value = 5
Dim MyDerivedObject As New DerivedClass
MyDerivedObject.Name = NameOf(MyDerivedObject)
MyDerivedObject.Value = 5.3
ProcessClass(MyBaseObject)
ProcessClass(MyDerivedObject)
Console.ReadLine()
End Sub
Sub ProcessClass(inClass As BaseClass)
Console.WriteLine($"{inClass.Name} {inClass.Value}")
End Sub
Public Class BaseClass
Public Property Name As String
Public Property Value As Object
End Class
Public Class DerivedClass
Inherits BaseClass
Overloads Property Value As Double
Get
Return CDbl(MyBase.Value)
End Get
Set(value As Double)
MyBase.Value = value
End Set
End Property
End Class
MyBaseObject 5
MyDerivedObject 5.3

Related

How to create a list of multiple object types and preserve methods/properties?

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.

How do I determine if a class member exists?

I have a public subroutine that is called by many classes. However, I now need to do something in that subroutine that only pertains to a small number of the classes that call it. So instead of going back and adding the property to all of the existing classes, I would like to simply check to see see if that class has that property and if so, then do something with it. But I can't seem to figure out how to simply check for the existence of the member without getting an error.
For example:
Public Class_1
Public a1 as string = ""
Public Sub New()
' when a button is clicked call subroutine "check()"
End Sub
End Class
Public Class_2
Public a1 as string = ""
Public a2 as integer = 0
Public Sub New()
' when a button is clicked call subroutine "check()"
End Sub
End Class
Public Class whatever
Public Sub check(sender as object)
If sender.a2 = 0 then
' do something
End if
End Sub
End Class
I have tried such things as
If not(sender.a2 is nothing) then
If isnothing(sender.a2) then
But I can't get past the fact that I get an error simply by using "sender.a2" since a2 is not always a member of the sender.
How can I check to see if a2 is a member of the sending class without using "sender.a2"?
If you want to see that a field exists you need this:
Dim fi As FieldInfo = sender.GetType().GetField("nameOfFieldHere")
If fi IsNot Nothing
'field exists now get the value
Dim o As Object = fi.GetValue(sender)
End If
Take a look at the documentation Type.GetField Method (String)
There are also overloads available too.
You can test that the Object you are referencing is of a certain type before attempting to use it. Once you've determined it's the right type, you can safely cast to it and then use the right properties like so:
If (TypeOf sender Is Class_2) Then
Dim castObj As Class_2 = CType(sender, Class_2)
'We can now access castObj.a2
End If
If there are multiple classes with the property, it would be sensible to create an Interface which states they have the a2 property and have them all implement it. You can then test their type against the new Interface instead of Class_2.
An alternative option is to use class inheritance to implement a default public method for all classes, and override it in your special Class_2 case for your subclasses.

How to find nested classes in VB.Net using reflection?

I have the following class :
Public Class F
Public Class A
Public Class C
End Class
End Class
Public Class B
End Class
End Class
And I'm writing a function to return the embedded classes of class F. Basically I'm expecting the function to return A & B types...
Public Function FindInternalClasses(ByVal TBaseType As Object) As List(Of Type)
Dim baseType = TBaseType.GetType
Dim assembly = baseType.Assembly
Dim Output As New List(Of Type)
For Each Item In assembly.GetTypes
If Item.IsSubclassOf(baseType) Then
Output.Add(Item)
End If
Next
Return Output
End Function
When running this function, it always return nothing. (The condition "If Item.IsSubclassOf(baseType)" is always false.)
Does anyone know please what is missing to this code ?
I am not sure what your starting point is, so I am guessing. SubClassing (original post) refers to one class inheriting from some parent class:
Public [MustInherit] Class FooBar
...
End Class
Public Class Foo : Inherits FooBar
...
End Class
Properties and methods defines in FooBar are inherited by Foo which may be overridden or shadowed out. But that is not what you have. Class A and B are simply nested classes of F, and C is nested within F.B.
These are easy to find using the baseType, which appears to be an instance in your code:
Dim baseType = TBaseType.GetType
For Each Item In baseType.GetNestedTypes
Console.WriteLine(Item.FullName)
Next
To find nested Types from a Type rather than instance, use Dim baseType = GetType(FooBar) as the starting point. If you know a bit about what you are after, you can get more specific by specifying BindingFlags. For instance include only nested Types which are private:
For Each Item In baseType.GetNestedTypes(BindingFlags.NonPublic)
...
But it need not be a loop:
Dim Output = New List(Of Type)(TBaseType.GetType.GetNestedTypes(BindingFlags.Public))
I am not sure why your code goes thru all the Types in the assembly since you have the starting baseType / actual outer type; if/when there is some good reason for that, use Hans' method.
You are almost there. You have about 95% of the function. The following should do the trick.
Imports System.Reflection
Module Module1
Sub Main()
Dim obj As New F()
Dim result = GetSubClasses(obj)
For Each t As Type In result
Console.WriteLine(t)
Next
Console.ReadLine()
End Sub
Public Function GetSubClasses(ByRef obj As Object) As List(Of Type)
Dim baseType = obj.GetType()
Dim output As New List(Of Type)
For Each t As Type In Assembly.GetExecutingAssembly().GetTypes()
If t.IsSubclassOf(obj.GetType()) Then
output.Add(t)
End If
Next
Return output
End Function
End Module
Public Class F
End Class
Public Class A : Inherits F
End Class
Public Class B : Inherits F
End Class
Public Class C : Inherits A
End Class
Output of the following would be:
ConsoleApplication1.A
ConsoleApplication1.B
ConsoleApplication1.C
A and B inherit from F. Because C inherits from A, it is also considered a subclass of F.
They are nested types. Nested types don't have any inheritance relationship to their outer type so IsSubclassOf() cannot work. The only property that makes them distinctive is the Type.DeclaringType property, it references their outer type. So your code ought to look like:
Public Function FindNestedTypes(ByVal outerType As Type) As List(Of Type)
Dim output As New List(Of Type)
For Each item In outerType.Assembly.GetTypes()
Dim declarer = item.DeclaringType
Do While declarer IsNot Nothing
If declarer Is outerType Then
output.Add(item)
Exit Do
End If
declarer = declarer.DeclaringType
Loop
Next
Return output
End Function
Sample usage:
For Each t As Type In FindNestedTypes(GetType(F))
Console.WriteLine(t.FullName)
Next
Output:
ConsoleApplication1.F+A
ConsoleApplication1.F+B
ConsoleApplication1.F+A+C
If you don't want to find class C then remove the Do While loop.

VB.NET - Access shared member through Type

I'm wondering if this is possible. In my Test function below I want to
Check if T is of type BaseClass; throw an error if it's not.
Get the value of SomeText property of BaseClass.
Class BaseClass
Public Shared Property SomeText As String
End Class
Class Class1
Inherits BaseClass
Public Sub New()
SomeText = "Class 1 here"
End Sub
End Class
...
Sub Main
Test(GetType(Class1))
Test(GetType(Class2))
End Sub
Sub Test(T As Type)
' Task 1: Check if T is of type BaseClass
' Task 2: Get the value of SomeText property of BaseClass
End Sub
You can keep your test signature and do the following:
If T.BaseType Is GetType(BaseClass) Then
Dim CurrentValue as String = BaseClass.SomeText
Else
Throw New ArgumentException("T must inherit from BaseClass")
End If
Since this never actually creates an instance of your type, you may not get the string you are expecting. If you haven't created an instance elsewhere you will only have an empty string.
You can generate an instance of your class from the type you sent if that is your goal:
If T.BaseType Is GetType(BaseClass) Then
Dim currentValue As String = CType(Activator.CreateInstance(T), BaseClass).SomeText
Else
Throw New ArgumentException("T must inherit from BaseClass")
End If
Edit based on Comment about further subclassing:
If you want it to include the base type or any descendants you could use this if instead:
If T Is GetType(EventBase) OrElse T.IsSubclassOf(GetType(EventBase)) Then
End If
Rewrite test as follows (updated to reflect change to question):
Sub Test(T As Type)
' Task 1: Check if T is of type BaseClass
If T.IsSubclassOf(GetType(BaseClass)) Then
' Task 2: Get the value of SomeText property of BaseClass
Dim sValue As String
' Create an unused instance of BaseClass to ensure the current value of SomeText is assigned
Dim oClass As BaseClass = DirectCast(System.Activator.CreateInstance(T), BaseClass)
' Get the value from BaseClass since it is a shared instance
sValue = BaseClass.SomeText
Else
Throw New ArgumentException("T did not inherit from BaseClass")
End If
End Sub

vb.net: calling constructor when using generics

I'm not sure if this is possible or not.
I have a number of different classes that implement interface IBar, and have constructors that take a couple of values. Rather than create a bunch of almost identical method, is it possible to have a generic method that will create the appropriate constructor?
private function GetFoo(Of T)(byval p1, byval p2) as List(Of IBar)
dim list as new List(Of IBar)
dim foo as T
' a loop here for different values of x
foo = new T(x,p1)
list.Add(foo)
' end of loop
return list
end function
I get:
'New' cannot be used on a type parameter that does not have a 'New' constraint.
Unfortunately not - .NET generics only allow you to constrain a generic type to have a parameterless constructor, which you can then call with New T()... you can't specify a particular set of parameters.
If you don't mind making your types mutable, you could create an interface which containing a method with the relevant parameters, make all your types implement the interface, and then constrain the type to implement that method and have a parameterless constructor, but it's not ideal.
Another option is to pass in an appropriate Func which takes x and p1 and returns a new T each time. That would certainly be easy to use from C# - not quite so easy in VB IIRC, but worth considering nevertheless.
Expanding on Jon Skeet's answer, here's a possible solution using a Func parameter:
Private Function GetFoo(Of T As IBar)(ByVal p1 As Object, ByVal p2 As Object, ctor As Func(Of Integer, Object, T)) As List(Of IBar)
Dim list As New List(Of IBar)
Dim foo As T
For x = 1 To 10
foo = ctor(x, p1)
list.Add(foo)
Next
Return list
End Function
usage would be similar to
GetFoo(1, 2, Function(i, o) New BarImpl(i, o))
It is possible to cal, a constructor even if it is not specified in generic constraints. See the example below.
'This base class has no constructor except the default empty one
Public Class MyBaseClass
End Class
'this class inhetits MyBaseType, but it also implements a non empty constructor
Public Class MySpecializedClass
Inherits MyBaseClass
Public Sub New(argument As String)
End Sub
End Class
Public Function CreateObject(Of ClassType As MyBaseClass)(argument As String) As ClassType
'First, get the item type:
Dim itemType As Type = GetType(ClassType)
'Now we can use the desired constructor:
Dim constructor As ConstructorInfo = itemType.GetConstructor(New Type() {GetType(String)})
If constructor Is Nothing Then
Throw New InvalidConstraintException("Constructor ""New(String)"" not found.")
Else
Dim result As ClassType = constructor.Invoke(New Object() {argument})
Return result
End If
End Function
Public Sub RunTest()
Try
Console.WriteLine("+----------------------------------------------------+")
Console.WriteLine("Trying to create a instance of MyBaseClass")
Console.WriteLine("+----------------------------------------------------+")
Dim myobject As MyBaseClass = CreateObject(Of MyBaseClass)("string value")
Console.WriteLine(myobject)
Console.WriteLine("Instance of MyBaseClass created")
Catch ex As Exception
Console.WriteLine(ex)
End Try
Try
Console.WriteLine("+----------------------------------------------------+")
Console.WriteLine("Trying to create a instance of MySpecializedClass")
Console.WriteLine("+----------------------------------------------------+")
Dim myobject As MyBaseClass = CreateObject(Of MySpecializedClass)("string value")
Console.WriteLine(myobject)
Console.WriteLine("Instance of MySpecializedClass created")
Catch ex As Exception
Console.WriteLine(ex)
End Try
End Sub
Here is my answer.
Public CreateObject(Of T)() As T
Dim newObj = Activator.CreateInstance(GetType(T), YourParameterHere)
Return newObj
End Function
This will give you the new object. You can pass any parameters to this function.