Decoupling: Unable to implement indirect referencing - vb.net

There are three parts to my code A, B, C. B is a class library which references A which is also a class library. In A, a class MathOperations is defined which contains a function ADD. The part B has a class Factory which in-turn has a function createMathsObject which returns an object of MathOperations type. The part C refers to B. I can do away with part B and create the object of MathOperations type in part C and make C reference A directly where that class is defined. However I am trying to see how I can de-couple sections of the code, in this case A and C.I give the code for each of the three parts A, B and C. The error message that I get is mentioned later for which I need your help.
Part A: Class Library - Named as MathLibrary.dll
Public Class MathOperations
Function ADD(ByVal a As Integer, ByVal b As Integer) As Integer
Return (a + b)
End Function
End Class
Part B: Class Library - Named as FactoryMathsLibrary.dll which references MathLibrary.dll
Imports MathLibrary
Public Class Factory
Shared Function createMathsObject() As MathOperations
Return New MathOperations()
End Function
End Class
Part C: Exe file - which references FactoryMathsLibrary.dll
Imports FactoryMathsLibrary
Module Module1
Sub Main()
Dim a, b As Integer
Dim m As Object = Factory.createMathsObject() 'Error comes here
Console.Write("Enter 1st value: ")
a = Console.ReadLine()
Console.Write("Enter 2nd value: ")
b = Console.ReadLine()
Console.WriteLine("a + b = {0}", m.ADD(a, b))
Console.ReadLine()
End Sub
End Module
I was under the impression that since C references B and B references A, everything would be fine. But I am getting an error in the line Dim m As Object = Factory.createMathsObject(). The error message is as follows: Can someone pls help with the reason behind the error and the solution??

I did exactly what you did, in your exe-project you haven't added reference to "MathLibrary.dll". You can't include a library that references to another library without including that second library in your exe-project too (Unless you merge them into one)! if you won't include it too, you will have exactly the issue that you are having.
And so, you only have to add reference to "FactoryMathsLibrary.dll" and to "MathLibrary.dll"
Project > project_name Properties... > References > Add... > MathLibrary.dll > OK
❌ Without reference
✔️ With reference

Related

creating a shared variable with inheritance

My question is in relation to the below question:
In VB.net, How can I access to a function in a class from an other function in a nested class?
By setting the variable h shared, are you making that variable available to all instances of the class as a single or static variable thereby creating the possibility for problems in the asker's future endeavors? Or is my understanding of VB.net skewed?
If I'm right would that mean that the code would the need to be arranged like this:
Class N
Dim h
Class n
Implements iInterface
Sub f()
h = 5
End Sub
End Class
End Class
And instead create an instance of the object to use in consuming code?
A shared variable isn't part of the instantiated object. If you write
Dim o As New N
o.h = 1
Assuming h is shared, you will get a warning. You have to call it like this.
N.h = 1
When you have code in the class itself, you don't need to specify the class name. His code is actually
Class N
Shared h = 4
Class n
Implements iInterface
Sub f()
N.h = 5
End Sub
End Class
End Class
Maybe this will help you understand it a bit more. This clearly show that each instance of n will be sharing the same h variable. Let's add a new function
Class N
Shared h = 4
Class n
Implements iInterface
Sub f()
h = 5
End Sub
Sub ff()
h = 12
End Sub
Function GetH() As Integer
Return h
End Sub
End Class
End Class
Dim o1 As New n
Dim o2 As New n
o1.f()
o2.ff()
Console.WriteLine(o1.GetH()) ' This will print 12
Console.WriteLine(o2.GetH()) ' This will print 12
I think his question didn't have enough information to indicate if the shared variable will cause problem or not.

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.

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

Declaring global, static variables

I'm trying to set a global variable in Visual Studio, but I can't make it static. Is there any way for me to set the variable as static and share it across different methods, or some way to save the variable each time it changes?
You have two options:
1 - Create a class that contains a Shared variable (this is the same as a static variable in C#)
Public Class GlobalVariables
Public Shared Bar As String
End Class
You can then access this using the class name:
GlobalVariables.Bar = "Hello world"
2 - Create a module (this is akin to a static class in C#)
Public Module GlobalVariables
Public Bar As String
End Module
You can then access this value in code like this:
Bar = "Goodbye cruel world"
If you use the number 1 option presented by #Matt Wilko, you can reference the shared member either through an object instance of the class or by referencing the class without an object reference. Both point to and increment the same variable and therefore reference the same value. Although, the Visual Studio compiler provides a warning about referencing an object instance and says that it will not be evaluated, it still compiles. The compiler's recommendation is to use the class name.
Public Class GlobalVariables
Public Shared Foo As Integer
End Class
Insert the following into a form and call IncrementIntegers() from a button click event procedure and you will find that myGlobalVariables.Foo and GlobalVariables.Foo both return 20.
Private Sub IncrementIntegers()
Dim myGlobalVariables As New GlobalVariables
myGlobalVariables.Foo = 0
GlobalVariables.Foo = 0
myGlobalVariables.Foo += 10
GlobalVariables.Foo += 10
Dim iLocalInt1 = myGlobalVariables.Foo
MessageBox.Show("myGlobalVariables.Foo = " & iLocalInt1.ToString)
Dim iLocalInt2 = GlobalVariables.Foo
MessageBox.Show("GlobalVariables.Foo = " & iLocalInt2.ToString)
End Sub
Note that with option 1, Foo must be qualified with either the class name or an object name. With option 2, it is a module and not a class so an object reference cannot be created. The public variable can be referenced without qualifying it with the module name unless a variable with the same name appears in another module in which case the compiler with throw a name conflict error. For example,
Public Module1
Public Foo As String
End Module
Public Module2
Public Foo As String
End Module
Remove Module2 and Foo can be called unqualified from anywhere.
Foo = "Happy birthday"
With Module2 present, Foo must be qualified with the name as both point to different variable and represent different and independent values.
Module1.Foo = "Goodbye cruel world"
Module2.Foo = "Hello new world"