Consider the following example in VB.NET
Module Module1
Sub Main()
Dim myCycle As Cycle
'Here I am making a Superclass reference to hold a subclass object
myCycle = New SportsCycle()
Console.WriteLine("----Cycle Details--------")
'Using this Object I am accessing the property Wheels of the Superclass Cycle
Console.WriteLine("Number Of Wheels: " & myCycle.Wheels)
'Using this Object I am accessing the property getTyp of the Subclass Cycle
Console.WriteLine("Type Of Cycle: " & myCycle.getTyp) 'Line #1(This Line is showing error)
Console.WriteLine("--------------------------")
Console.ReadKey()
End Sub
End Module
Public Class Cycle
Private num_of_wheels As Integer
Property Wheels As Integer
Get
Return num_of_wheels
End Get
Set(ByVal value As Integer)
num_of_wheels = value
End Set
End Property
End Class
Public Class SportsCycle
Inherits Cycle
Private type As String
Sub New()
type = "RAZORBIKE"
Wheels = 2
End Sub
ReadOnly Property getTyp As String
Get
Return type
End Get
End Property
End Class
The above program is showing an error which states that "'getTyp' is not a member of
Question.Cycle in Line # 1" here 'Question' is my Project name.
Kindly clarify this concept to me. What needs to be done?
Try:
DirectCast(myCycle, SportsCycle).getTyp
The reason for this is that Cycle does not contain this property where as SportsCycle does. As SportsCycle inherits from Cycle, you can cast to SportsCycle to access the property.
Related
This question already has an answer here:
VBA inheritance pattern
(1 answer)
Closed 4 months ago.
I’m creating a set of User Defined Classes using Implements. Some (but not all) of the Properties and Methods of the implemented classes use exactly the same code in each implemented class. What I would like to do is move that code to one place, to avoid repeating myself.
An minimal example to demonstrate the requirement:
Class cMasterClass
Option Explicit
Private pMyClasses As Collection
Public Property Get Item(idx As Long)
Set Item = pMyClasses.Item(idx)
End Property
Public Property Get SomeProperty() As String
SomeProperty = "Master Class"
End Property
Public Sub AddClass(Name As String, Instance As Long)
Dim NewClass As cTemplateClass
Select Case Instance
Case 1
Set NewClass = New cMyClass1
Case 2
Set NewClass = New cMyClass2
End Select
NewClass.Init Me, Name
pMyClasses.Add NewClass, Name
End Sub
Private Sub Class_Initialize()
Set pMyClasses = New Collection
End Sub
Class cTemplateClass
Option Explicit
Public Property Get Name() As String: End Property
Public Property Get Parent() As cMasterClass: End Property
Public Property Get SomeProperty() As String: End Property
Public Sub Init(Parent As cMasterClass, Name As String): End Sub
Class cMyClass1
Option Explicit
Implements cTemplateClass
Private pParent As cMasterClass
Private pName As String
Public Property Get cTemplateClass_Name() As String: cTemplateClass_Name = pName: End Property
Public Property Get cTemplateClass_Parent() As cMasterClass: Set cTemplateClass_Parent = pParent: End Property
Public Property Get cTemplateClass_SomeProperty() As String
cTemplateClass_SomeProperty = "Some String from MyClass 1"
End Property
Public Sub cTemplateClass_Init(Parent As cMasterClass, Name As String)
Set pParent = Parent
pName = Name
End Sub
Class cMyClass2
Option Explicit
Implements cTemplateClass
Private pParent As cMasterClass
Private pName As String
Public Property Get cTemplateClass_Name() As String: cTemplateClass_Name = pName: End Property
Public Property Get cTemplateClass_Parent() As cMasterClass: Set cTemplateClass_Parent = pParent: End Property
Public Property Get cTemplateClass_SomeProperty() As String
cTemplateClass_SomeProperty = "Some String from MyClass 2"
End Property
Public Sub cTemplateClass_Init(Parent As cMasterClass, Name As String)
Set pParent = Parent
pName = Name
End Sub
Standard Module
Sub Demo()
Dim MyMasterClass As cMasterClass
Set MyMasterClass = New cMasterClass
MyMasterClass.AddClass "Example class 1", 1
MyMasterClass.AddClass "Example class 2", 2
Dim SomeInstance As cTemplateClass
Set SomeInstance = MyMasterClass.Item(1)
Debug.Print "Instance 1", SomeInstance.Name, "Parent", SomeInstance.Parent.SomeProperty
Set SomeInstance = MyMasterClass.Item(2)
Debug.Print "Instance 2", SomeInstance.Name, "Parent", SomeInstance.Parent.SomeProperty
End Sub
Notice that in cMyClass1 and cMyClass2 the code for Init, Name and Parent are identical (but SomeProperty is not)
How could I move the common code from the individual classes into one place (I know the Template class cannot contain the common code)?
How could I move the common code from the individual classes into one place (I know the Template class cannot contain the common code)?
Hi Chris, actually the Template class can contain that common code and be used with Implements to ensure that interface implementation. I am late with this answer and I am not sure if this can help you but anyway ... :). What you could consider would be to actually move the parent and name to your template class and use init to fill them and at the same time with using implements create instance of the template so your classes ensure that interface and they can redirect the code to the template at the same time. If you wish have a look here for another example. HTH, Dan
Change the child classes a bit:
Option Explicit
Implements cTemplateClass
'Move this to your template class:
'Private pParent As cMasterClass
'Private pName As String
'and declare the varaible of template class here instead:
Private pTemplate As cTemplateClass
Public Property Get cTemplateClass_Name() As String
'Redirect the property to template class instance
'cTemplateClass_Name = pName
cTemplateClass_Name = pTemplate.Name
End Property
Public Property Get cTemplateClass_Parent() As cMasterClass
'Redirect the property to template class instance
'Set cTemplateClass_Parent = pParent
Set cTemplateClass_Parent = pTemplate.Parent
End Property
Public Sub cTemplateClass_Init(Parent As cMasterClass, Name As String)
' Instantiate here the template class and redirect the Init
' Set pParent = Parent
' pName = Name
Set pTemplate = New cTemplateClass
pTemplate.Init Parent, Name
End Sub
Public Property Get cTemplateClass_SomeProperty() As String
cTemplateClass_SomeProperty = "Some String from MyClass 2"
End Property
And anhance the template class a bit too:
Option Explicit
'Move this here from your 'derived' classes:
Private pParent As cMasterClass
Private pName As String
Public Property Get Name() As String
Name = pName
End Property
Public Property Get Parent() As cMasterClass
Set Parent = pParent
End Property
Public Sub Init(Parent As cMasterClass, Name As String)
Set pParent = Parent
pName = Name
End Sub
Public Property Get SomeProperty() As String
End Property
Assume the following example class which mimics the type of class generated from an XSD file:
Public Class MyClass
Public Class MyInnerClass1
Public Class MyInnerInnerClass1
Public Property MyProp1 as string
Public Property MyProp2 as string
...
End Class
...
Public Property MyInnerInnerClassProp1 as MyInnerInnerClass1
End Class
Public property MyInnerClassProp1 as MyInnerClass1
Public property MyInnerClassProp2 as MyInnerClass2
...
End Class
Notice that there are no constructors. The level of inner classes, in this particular case, can go 5 levels deep, possibly circularly, before hitting a base property such as Property MyProp1 as string.
How can I recursively iterate through ALL of the public writable properties and initialize them as new instances of that object type without constructors?
For example, here is my current code which only goes one level deep at the moment?
Private Shared Sub InitProperties(obj As Object)
For Each prop As Object In obj.[GetType]().GetProperties(BindingFlags.[Public] Or BindingFlags.Instance).Where(Function(p) p.CanWrite)
Dim type__1 = prop.PropertyType
Dim constr = type__1.GetConstructor(Type.EmptyTypes)
'find paramless const
If type__1.IsClass Then
Dim propertyInstance = DirectCast(FormatterServices.GetUninitializedObject(type__1.GetType()), Object)
'Dim propInst = Activator.CreateInstance(type__1)
'prop.SetValue(obj, propInst, Nothing)
InitProperties(propertyInstance)
End If
Next
End Sub
I did some small edits to your code to get it to work on the example class you provided. (Although I did change the string properties to Integer to avoid one error.) I also added an argument for limiting the number of recursive calls, and a check that a property is equal to nothing before initializing it. (This check will only make a difference if you have circular references between static classes.)
Private Shared Sub InitProperties(obj As Object, Optional ByVal depth As Integer = 5)
For Each prop As PropertyInfo In obj.GetType().GetProperties(BindingFlags.Public Or BindingFlags.Instance).Where(Function(p) p.CanWrite)
Dim type__1 As Type = prop.PropertyType
If type__1.IsClass And IsNothing(prop.GetValue(obj, Nothing)) And depth > 0 Then
Dim propertyInstance As Object = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(type__1)
prop.SetValue(obj, propertyInstance, Nothing)
InitProperties(propertyInstance, depth - 1)
End If
Next
End Sub
When creating a object based on a class. There are certain properties that I prefer to not be value 0 or nothing. So I would like to set the initial value to 1.
Is this best done via the constructor?
Class Product
Public Property Price As Decimal
Public Sub New()
Price = 1
End Sub
End Class
Or can you also write it as following? Does this second version make the value fixed at 1 or can you also alter the value if It's written like this?
Class Product
Public Property Price As Decimal = 1
End Class
Either way you do it, it'll function the same, however do defer to how your team normally does it to maintain consistency.
However, if you do have instances where you may open up the constructor to allow setting of those properties on initialization based on some argument given to the constructor, I would opt to always setting it in the constructor for consistency. If the property always has a default value of X on initialization then inline it at the top so it stands out.
Basic Example:
Class Product
Public Property Price As Decimal = 1
Public Property Quantity As Integer
Public Sub New()
Quantity = 0
End Sub
Public Sub New(quantity As Integer)
Quantity = quantity
End Sub
End Class
At the end its the same, in both cases you can change the values.
No difference, you can alter the value unless it's const or readonly. If you do the second, the compiler will sort of convert it like your first version. Here's a little program that'll show you. This will display 0 and then 1.
Module Module1
Sub Main()
Dim o As New B
Console.ReadLine()
End Sub
End Module
MustInherit Class A
Public Sub New()
Show()
End Sub
Public MustOverride Sub Show()
End Class
Class B
Inherits A
Private test As Integer = 1
Public Sub New()
MyBase.New()
' Value for test is being set here
Show()
End Sub
Public Overrides Sub Show()
Console.WriteLine(test)
End Sub
End Class
With class module "Class1Test" as
Private pGreetings As Collection
Public Property Get Greetings() As Collection
Greetings = pGreetings
End Property
Public Property Let Greetings(Value As Collection)
pGreetings = Value
End Property
If I run the sub
Dim MyPhrases As Class1Test
Public Sub Test()
Set MyPhrases = New Class1Test
MyPhrases.Greetings.Add "Have a nice day"
End Sub
I get the a compile error "Argument not optional"
Why can't I add the string to the the collection myphrases.greetings ? Please forgive the newbie question. Just learning VBA.
A few things wrong.
Collection is an object, so you must use the Set keyword when assigning. Also in the Let procedure for consistency in naming conventions, I would use lGreetings instead of Value although that should not really matter.
Private pGreetings As Collection
Public Property Get Greetings() As Collection
Set Greetings = pGreetings
End Property
Public Property Let Greetings(lGreetings As Collection)
Set pGreetings = lGreetings
End Property
This will still raise an 91 error (Object variable or with block not set) because you have not instantiated the collection object. Probably the way you should do this is in the class module's Initialize routine.
Private Sub Class_Initialize()
Set pGreetings = New Collection
End Sub
I've seen some other responses about this and they talk about interfaces but I'm pretty sure you can do this with classes and base classes but I can't this to work.
Public Class Behavior
Private _name As String
Public ReadOnly Property Name As String
Get
Return _name
End Get
End Property
Public Property EditorUpdate As Boolean
Public Sub New(ByVal name As String)
_name = name
EditorUpdate = False
End Sub
Public Overridable Sub Update()
End Sub
' runs right away in editor mode. also runs when in stand alone game mode right away
Public Overridable Sub Start()
End Sub
' runs after game mode is done and right before back in editor mode
Public Overridable Sub Finish()
End Sub
' runs right when put into game mode
Public Overridable Sub Initialize()
End Sub
' runs when the game is complete in stand alone mode to clean up
Public Overridable Sub Destroy()
End Sub
End Class
Public Class CharacterController
Inherits Behavior.Behavior
Public Sub New()
MyBase.New("Character Controller")
End Sub
Public Overrides Sub Update()
' TODO: call UpdateController()
' THINK: how can UpdateController() get the controller entity it's attached to?
' Behaviors need a way to get the entity they are attached to. Have that set when it's assigned in the ctor?
End Sub
End Class
Dim plugins() As String
Dim asm As Assembly
plugins = Directory.GetFileSystemEntries(Path.Combine(Application.StartupPath, "Plugins"), "*.dll")
For i As Integer = 0 To plugins.Length - 1
asm = Assembly.LoadFrom(plugins(i))
For Each t As Type In asm.GetTypes
If t.IsPublic Then
If t.BaseType.Name = "Behavior" Then
behaviorTypes.Add(t.Name, t)
Dim b As Behavior.Behavior
b = CType(Activator.CreateInstance(t), Behavior.Behavior)
'Dim o As Object = Activator.CreateInstance(t)
End If
End If
Next
Next
When it tries to convert whatever Activator.CreateInstance(t) returns to the base class of type Behavior I'm getting invalid cast exception. That type should be of CharacterController which is defined as a child of Behavior so why wouldn't it let me cast that? I've done something like this before but I can't find my code. What am I missing?
This may not be an answer to your question (it also might resolve your exception -- who knows), but it is something that needs to be pointed out. These lines:
If t.IsPublic Then
If t.BaseType.Name = "Behavior" Then
Should really be changed to one conditional like this one:
If t.IsPublic AndAlso (Not t.IsAbstract) AndAlso _
GetType(Behavior.Behavior).IsAssignableFrom(t) Then
Otherwise, if somebody defines a random type called "Behavior" in their own assembly and derives it from another type, your code will think it is a plugin. Additionally, if someone derives your Behavior type and then derives that type (two levels of inheritance) this code will incorrectly skip over that type. Using the IsAssignableFrom method is a quick and easy way to ensure that one type does actually derive from the specific type you want (instead of any type that shares the same name), even if there is another type in between your types in the inheritance tree. The additional check against t.IsAbstract will also ensure that you don't try to instantiate an abstract subtype of your base plugin type.
This works for me:
Dim ctor As Reflection.ConstructorInfo = _
t.GetConstructor(New System.Type() {})
Dim o As Object = ctor.Invoke(New Object() {})
Dim plugin As Plugin = TryCast(o, Plugin)
(If I find t, I invoke the parameterless constructor.)
[I just realized this is probably what Activator.CreateInstance does, so I replaced my code with yours and it worked your way -- so this probably won't help you]