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

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.

Related

Passing a derived class containing an overloaded property as a parameter

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

Shared function that infers the class type and can be returned in a list

I would like to create a shared function that returns a list of instances of the classes type. Currently this is what my code looks like
class MyClass
Implements BusinessObject
Shared Function LoadAll(Of T As {BusinessObject, New})() As IEnumerable(Of T)
Dim helper = New SQLHelper()
Return helper.LoadDataTableFromDatabase("LoadTable", LoadAllProcedureName).Rows.Cast(Of DataRow).Select(Function(s) New T().FillDataRow(Of T)(s))
End Function
End Class
class MyDerivedClass Implements MyClass
End MyClass
When I go to use it, I have to use it like this:
MyDerivedClass.LoadAll(Of MyDerivedClass)()
I would like to be able to infer the type, instead of having to use the (Of MyDerivedClass) so that my code looks like MyDerivedClass.LoadAll().
Any help or keywords that I am missing to achieve this would be greatly appreciated.
Here is an extension method which (theoretically) would work on any class you define:
Public Module Module1
<Extension()> _
Public Function LoadAll(Of T As {BusinessObject, New})(ByVal x As T) As IEnumerable(Of T)
Dim LoadAllProcedureName As String = "LoadAllProcedure"
Dim helper = New SQLHelper()
Return helper.LoadDataTableFromDatabase("LoadTable", LoadAllProcedureName).Rows.Cast(Of DataRow).Select(Function(s) New T().FillDataRow(Of T)(s))
End Function
Public Sub Main()
Dim dC As New DerivedClass()
Dim allDc As IEnumerable(Of DerivedClass) = dC.LoadAll()
'::: Somewhat shorter syntax
Dim allDC As IEnumerable(Of DerivedClass) = (New DerivedClass()).LoadAll()
End Sub
End Module
But, as others have pointed out, this doesn't really clean anything up for you. More to the point, you are going to have to type (Of DerivedClass) in whatever variable you intend on populating with your enumerated DerivedClass, no?
And from what I can tell, you cannot have Shared extension methods -- should you be thinking that is the way to go.

instantiate object ONLY from other class in VB.NET

I have 2 classes A and B (in VB.NET).
I want the only way to create an object of B class was by using a mehtod of A.
Examples:
You could do:
Dim objectA as new A
Dim objectB as B = objectA.getAobject()
BUT you couldnĀ“t do:
Dim objectB as new B
Thanks!
Edit: in "You could do" section I wanna mean "Dim objectB as B = objectA.getAobject()"
You can make a private constructor in B:
Private Sub New()
End Sub
If there are no public constructors, this will block you from writing code like this anywhere but within B itself:
Dim objectB as new B
However, this requires you to write code like that somewhere in B, or you won't be able to ever create an instance of B anywhere. Typically a Shared method is the place to do this:
Friend Shared Function Create() As B
'...
Return New B
End Function
Note the Friend access modifier. Now, if you have an assembly (class library project) containing only A and B, only code inside that assembly (only A and B) will be able to use that function. Add to that a method in A that looks like this:
Public Shared Function getObject() As B
Return B.Create()
End Function
And we've met all your stated objectives. Code that references this assembly will be able to do this:
Dim objectB as B = A.getAobject()
But will not be able to do either of these:
Dim objectB as new B
Dim objectB As B = B.Create()
You could just check in the constructor of B, if your calling class is of type A
Class B
Sub New ()
' get parent frame and calling method
Dim frame As StackFrame = new StackFrame( 1, True )
Dim method As MethodBase = frame.GetMethod()
' for debug purposes
Dim methodName As String = method.DeclaringType.Name & "->" & method.Name
Console.WriteLine(methodName)
' throw exception
If (method.DeclaringType.Equals(typeof(A)) = False) {
throw new Exception("Not called from class A!!")
End If
End Sub
End Class
Create a new Project of type Class Library and add code similar to this for Class A and Class B
Public Class A
Public Function getObject() As B
Return New B()
End Function
End Class
Public Class B
Protected Friend Sub New()
Console.WriteLine("Constructor B called")
Console.ReadLine()
End Sub
End Class
Now the Constructor of Class B could only be called from inside code present in this project not from other assemblies
In your main project you need to add a reference to this new project and Imports its namespace.
Module Module1
Sub Main()
Dim a1 = New A()
' Dim b1 = New B() 'This will not compile at all'
Dim b2 = a1.getObject()
End Sub
End Module
Of course now you have to distribute two files instead of one....
The key for this to work is the different assembly and the keyword Protected Friend applied to the constructor of class B

How to filter object with specified type in generic collections in VB

given the following class structures:
Class Base {id}
Class Derived1 : Base {}
Class Derived2 : Base {}
Class MyList : System.Collection.Generic.List(Of Base)
Class Consumer { list as MyList }
(edit: there will be more than 2 derived classes, I listed out only 2 for simplicity)
I add the following property to MyList, trying to answer questions like "find out the number of elements with specified type, say Derived1, inside MyList?"
Public Overloads ReadOnly Property Count(ByVal objType As System.Type) As Integer
Get
Dim cnt As Integer = 0
For Each o As Object In Me
If (o IsNot Nothing) And (o.GetType.Equals(objType)) Then cnt += 1
Next
Return cnt
End Get
End Property
Q1: Are there anything to improve? If yes, please advise.
Thanks in advance
In case you use .NET 3.5 you could use Linq:
Dim myList As MyList;
...
Dim count = myList.OfType(Of Derived1).Count();
No need add the property at all.

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.