How can I check the final type of a variable? - vb.net

I have a BaseClass, a DerivedClass1 and a DerivedClass2 from a third party library. DerivedClass1 and DerivedClass2 both inherit from BaseClass.
There's a ContainerClass, from the same library, with a member variable ActiveItem, which can be of DerivedClass1 or DerivedClass2, so it is declared as BaseClass.
I want to know if ActiveItem is of DerivedClass1, as it can change in runtime without notice.
If I do
Dim isDerivedClass1 as boolean = TypeOf(oject.ActiveItem) Is DerivedClass1
then I get a compile time error, telling me that ActiveItem can never be of DerivedClass1 type.
I have tried several combinations of GetType and TypeOf but it doesn't seem possible to check this. I have also tried to declare an auxiliary DerivedClass1 variable and comparing their types, but haven't got any luck either.
Is there any workaround?
I guess I could do it with Reflection, but seems really overkill.
Edit:
The following code doesn't compile in vs2005 SP1.
Public Class Base
Public x As Integer
End Class
Public Class Derived1
Inherits Base
Public y As Integer
End Class
Public Class Derived2
Inherits Base
Public z As Integer
End Class
Public Class Unrelated
Public var As Base
End Class
Public Class Form1
Public Sub Test(ByVal obj As Unrelated)
Dim tst As Boolean
tst = TypeOf obj Is Derived1
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim obj As New Unrelated
obj.var = New Derived1
Test(obj)
End Sub
End Class
Edit:
It seems that the original problem was a fault in my side. I was checking against the wrong type (those silly third part libraries...)
However, I'm still trying to find the error in the code above.
Edit:
Again, my fault. I'm checking the Unrelated type against Base.

You code seems to be almost exactly right.
I've done this, which works fine:
Dim isDerivedClass1 As Boolean = TypeOf oject.ActiveItem Is DerivedClass1
Dim isDerivedClass2 As Boolean = TypeOf oject.ActiveItem Is DerivedClass2
Have I missed something?
EDIT: I think you just missed the var property in your edited code.
Public Sub Test(ByVal obj As Unrelated)
Dim tst As Boolean
tst = TypeOf obj.var Is Derived1
End Sub

You'll have to trust the compiler on this, it is convinced that DerivedClass1 does not inherit BaseClass. It doesn't get that wrong. That's either because it didn't see the Inherits clause in the DerivedClass1 declaration or because it picked a BaseClass definition from another assembly.
To fix the former problem, you have no alternative but to declare the ActiveItem as Object or to find another type that these classes have in common. Use the Object Browser. To fix the latter problem you'll have to change the Imports directive or spell out the full name of the BaseClass type (including namespace).

A simple possibility could be to use TryCast:
Dim isDerivedClass1 As Boolean = TryCast(object.ActiveItem, DerivedClass1) IsNot Nothing

Related

Can I make methods visible to some classes but not others

Quite simple; I've made a class with a method that's public since another class calls this method.
I would like the method only to show up on intellisense in the 2nd class which holds some special reference to the 1st one. Any other class or module should not be able to see the method.
Something along the lines of
Semi-Private (except for Class2) Sub ...
in the method of Class1, or in Class2
Can See Semi-Private methods of Class1
Instead of getting complicated with it, and using a roundabout approach, just use the tools meant for the job:
Class Module IFoo
Public Sub Bar()
' Interface methods are empty
End Sub
Class Module Foo
Private Type TFoo
Baz As String
End Type
Private this As TFoo
Implements IFoo
Private Sub Class_Initialize()
this.Baz = "I am a class that implements IFoo."
End Sub
Private Sub IFoo_Bar()
' Do Something
Debug.Print this.Baz
End Sub
Module Baz
Sub RunBaz()
Dim MyFoo As IFoo
' Note that this WILL NOT work. Nothing happens.
Set MyFoo = New IFoo
Debug.Print MyFoo.Bar
' Set MyFoo to be equal to a Foo (which implements IFoo)
Set MyFoo = New Foo
Debug.Print MyFoo.Baz
End Sub
This makes the methods only visible when the methods are being accessed through an interface which makes them public. Therefore, in order to use the methods in Foo we must first create an instance of Foo using an IFoo variable type.
Then, it is as simple as creating a new class which creates a Foo from an IFoo for its own use.
Class Module IImportantWorker
Public Sub DoSomethingImportant()
End Sub
Class Module ImportantWorker
Private Type TImportantWorker
Implementation As IFoo
End Type
Private this As TImportantWorker
Implements IImportantWorker
Private Sub Class_Initialize()
Set this.Implementation = New Foo
End Sub
Public Sub IImportantWorker_DoSomethingImportant()
this.Implementation.Bar
End Sub
You could get fancy from here and make a property of Foo that is exposed by IFoo that tells it whether or not it can work. This would have to be exposed through the IFoo interface (or, alternatively a separate interface so that the two interfaces must be used in conjunction).
Without locking the class (which I wouldnt recommend anyways, it seems foolish and pointless) Foo will still allow Bar if it is created as a IFoo. But if you just make Foo = New Foo then Foo will do nothing (or rather, expose nothing).
For additional resources, I highly recommend reading these excellent posts that go into greater depth about the processes:
Is VBA an OOP language, and does it support polymorphism?
https://rubberduckvba.wordpress.com/2016/06/16/oop-vba-pt-1-debunking-stuff/
https://rubberduckvba.wordpress.com/2016/07/05/oop-vba-pt-2-factories-and-cheap-hotels/
You may create the class that contains all properties and methods, name it BigClass:
Option Explicit
Dim i1 As Integer
Dim i2 As Integer
Dim i3 As Integer
Property Let var1(v As Integer)
i1 = v
End Property
Property Get var1() As Integer
var1 = i1
End Property
Property Let var2(v As Integer)
i2 = v
End Property
Property Get var2() As Integer
var2 = i2
End Property
Property Let var3(v As Integer)
i3 = v
End Property
Property Get var3() As Integer
var3 = i3
End Property
Then you can make classes that inherits BigClass methods, but not necessary all of them. For example, SmallClass1 that will use only var3 variable.
Option Explicit
Dim BigOne As BigClass
Private Sub Class_Initialize()
Set BigOne = New BigClass
End Sub
Private Sub Class_Terminate()
Set BigOne = Nothing
End Sub
Property Let var3(v As Integer)
BigOne.var3 = v
End Property
Property Get var3() As Integer
var3 = BigOne.var3
End Property
Then you can create SmallClass2 that use var2 and var1 but not var3. You get the point.
An clear example would be involved and lengthy and I have deposited one on my blog here VBA - Difference between Friend and Public with inter project references.
Essentially you use the Friend keyword but nothing happens until you split your code across workbooks which itself brings issues such as Instancing and factory functions. Example given should work though. Enjoy!

How do I attach to different classes with the same events at runtime?

Given two classes with identical events I'd like to be able to refer to them using one name. Similar to the following:
Public Class myclass1
Public event1()
End Class
Public Class myclass2
Public event1()
End Class
Here I'd like to be able to determine at run time which class to use:
Sub somefunction(select as integer)
Dim voidclass
if select = 1 then
voidclass = myclass1
else
voidclass = myclass2
end if
AddHandler voidclass.event1, AddressOf eventhappened
End Sub
Sub eventhappened()
msgbox("Event occured")
End Sub
Obviously there may be a better method for this example, but let's assume that class book1 already exists, and that I'm tasked with creating book2 and only modifying somefunction without modifying book1 itself.
The example above should result in the error event1 is not an event of 'Object'`.
It appears that properties and methods are fine with this, but events are not. How do I handle events in this situation?
The snippet does not match the question very well. It has an odd bug, the events in the classes are not declared Shared so the code must use a proper object reference. Not a type name. Maybe the answer is as simple as:
Private obj1 As myclass1
Private obj2 As myclass2
Sub somefunction(select as integer)
If select = 1 Then
AddHandler obj1.event1, AddressOf eventhappened
Else
AddHandler obj2.event1, AddressOf eventhappened
End If
End Sub
But presumably the real question is the same scenario, but now the variables declared as:
Private obj1, obj2
Untyped and thus Object. Yes, VB.NET does not support that. As with most quirks in VB.NET there is history behind this. VB never did support explicit late binding of events, only methods and properties. The scheme in the legacy versions was very quirky. You had to declare the variable with the WithEvents keyword and pick a specific name for the event handler. In other words:
Dim WithEvents obj1
Sub obj1_somethinghappened
'' handles the event1 event for obj1
End Sub
Or to put it another way, all events were late-bound. This scheme was not carried forward into VB.NET, too many practical problems with it. WithEvents still survives but now requires the Handles keyword on the method declaration. That doesn't do what you want it to do.
The VB.NET designers intentionally did not add late binding support to the AddHandler statement. I am not privy to the exact reason they decided this and can only guess. There is no technical reason it could not be added, it just requires the compiler to generate reflection code. One possible explanation is that they considered it too expensive. Another is that they deemed the runtime exceptions that are raised when there's a method signature mismatch too hard to interpret. I like that last one best, they are awfully ugly.
You'll have to use Reflection to get what you want. Use obj1.GetType().GetEvent("event1") to get the EventInfo, its Get/AddMethod() to add the event handler.
Fwiw, the C# language does support this in its dynamic keyword implementation. Maybe you can put a bug in their ear by asking for the feature. No real idea if this was requested before.
You can define an interface for the event and implement it in the classes then you can subscribe to this event through the interface reference. See below:
Public Interface INotifier
Event SomethingHappened()
End Interface
Public Class Class1
Implements INotifier
Public Event SomethingHappened() Implements INotifier.SomethingHappened
End Class
Public Class Class2
Implements INotifier
Public Event SomethingHappened() Implements INotifier.SomethingHappened
End Class
Module Module1
Dim notifiers As List(Of INotifier) = New List(Of INotifier) From
{
New Class1(),
New Class2()
}
Sub Main()
SubscribeToEventHandler(0)
End Sub
Private Sub SubscribeToEventHandler(ByVal index As Integer)
Dim notifier As INotifier = notifiers(index)
AddHandler notifier.SomethingHappened, AddressOf EventHandler
End Sub
Private Sub EventHandler()
End Sub
End Module

Setting Up These Types While Keeping It Properly Structured

I'm completely stuck in a situation and I have no idea on where to go from here. I'm creating a very large project, so my goal is to keep the code itself as clean as possible and keeping as many hacks as possible out of the mix.
Here is the situation.
I have a class called Woo_Type, it is the parent of my many derived classes.
Public MustInherit Class Woo_Type
Private Shared TypeList As New Dictionary(Of String, Woo_Type)
Public MustOverride Sub SetValue(ByVal val As Object)
Public MustOverride Function GetValue() As Object
Public Shared Function GetTypeFromName(ByVal name As String) As Woo_Type
Return TypeList(name)
End Function
Public Shared Sub AddType(ByVal name As String, ByVal def As Woo_Type)
TypeList.Add(name, def)
End Sub
End Class
I have many classes that Inherit from Woo_Type that have similar structures to this:
Public Class Woo_tpInt
Inherits Woo_Type
Private value As Integer = 0
Public Overrides Function GetValue() As Object
Return value
End Function
Public Overrides Sub SetValue(val As Object)
value = val
End Sub
End Class
I want to be able to do things like:
Woo_Type.GetTypeFromName("int")
And have it return something like the class or something...
At this point I'm really confused as to what I want and I didn't know if anybody had any suggestions. To make sure that GetTypeFromName worked correctly, I had in an Initializer sub the following:
Public Sub InitializeTypes()
Woo_Type.AddType("int", Woo_tpInt)
Woo_Type.AddType("String", Woo_tpInt)
End Sub
But I quickly realized that-that obviously doesn't work either.
So this may seem confusing but I'm basically wondering how to better structure this so that everything works...
What do you want to do with the result? Are you sure you don't simply need generics?
Public Class WooType(Of T)
Public Property Value As T
End Class
Public Class Test
Public Sub Foo()
Dim int As New WooType(Of Integer)
int.Value = 42
Dim str As New WooType(Of String)
str.Value = "Forty-Two"
End Sub
End Class
If what you want to do is get the type itself (as opposed to an object), I would recommend using reflection rather than trying to reinvent the wheel. For instance, to get the Woo_tpInt type, you could do this:
Dim a As Assembly = Assembly.GetExecutingAssembly()
Dim t As Type = a.GetType("WindowsApplication1.Woo_tpInt") ' Change WindowsApplication1 to whatever your namespace is
If you want to use a shorter name like "int" to mean "WindowsApplication1.Woo_tpInt", you could create a dictionary to store the translation table, for instance:
Dim typeNames As New Dictionary(Of String, String)
typeNames.Add("int", GetType(Woo_tpInt).FullName)
Dim a As Assembly = Assembly.GetExecutingAssembly()
Dim t As Type = a.GetType(typeNames("int"))

Create a shared IEqualityComparer

I'm doing some LINQ which requires a custom comparer, so I created a new class implementing IEqualityComparer. However, when I use it, I have to create an instance of it each time.
Dim oldListOnly = oldList.Except(newList, New MyEqualityComparer)
Dim newListOnly = newList.Except(oldList, New MyEqualityComparer)
I may be misunderstanding how .NET works, but it seems wasteful to create a new comparer each time. I really just want one instance (the equivalent of static in C++/C#).
So I tried creating a "static" class, which in vb.net is a module. But got an 'Implements' not valid in Modules error.
I then tried making the Equals and GetHashCode function shared methods on my class, but got this error: Methods that implement interface members cannot be declared 'Shared'.
Any ideas how to accomplish my goal here? Or am I simply misunderstanding what's going behind the scenes?
Your understanding is correct, although the waste is unlikely to be noticeable. For your situation, you could use the singleton pattern, which usually goes something like this:
Public Class MyEqualityComparer
Implements IEqualityComparer(Of whatever)
Private Sub New()
'no outsider creation
End Sub
Private Shared ReadOnly _instance As New MyEqualityComparer()
Public Shared ReadOnly Property Instance As MyEqualityComparer
Get
Return _instance
End Get
End Property
'other code
End Class
Why not simply do
Dim comparer = New MyEqualityComparer
Dim oldListOnly = oldList.Except(newList, comparer )
Dim newListOnly = newList.Except(oldList, comparer )
There needs to be an instance of a concrete type that implements IEqualityComparer. What you can do with a module, however, is define a public instance which is initialized to "New EqualityComparer". You can then pass that default instance to the Except method.
Something like:
Public Module MyComparer
Public acmeComparer As acmeCompareType
Public Class acmeCompareType
Implements IEqualityComparer(Of System.Drawing.Point)
Public Function Equals1(x As System.Drawing.Point, y As System.Drawing.Point) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of System.Drawing.Point).Equals
Return Math.Abs(x.X) = Math.Abs(y.X) AndAlso Math.Abs(x.Y) = Math.Abs(y.Y)
End Function
Public Function GetHashCode1(obj As System.Drawing.Point) As Integer Implements System.Collections.Generic.IEqualityComparer(Of System.Drawing.Point).GetHashCode
' Note that obj is a struct passed by value, so we can safely modify it here
' (without affecting the caller's instance)
obj.X = Math.Abs(obj.X)
obj.Y = Math.Abs(obj.Y)
Return obj.GetHashCode
End Function
End Class
End Module

.net dynamic loading

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]