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
Related
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
Often I have to write structures where logic is performed on each member variable for a class. For example, in this code I am finding anything that has changed between the object and another object, then returning the changes:
Public Class Foo
Public B1 As Boolean?
Public B2 As Boolean?
...
Public B1000 As Boolean?
Function GetChanges(F as Foo) As Foo
Dim Changes As New Foo()
If Not B1.Equals(F.B1) Then
B1 = F.B1
Changes.B1 = F.B1
End If
If Not B2.Equals(F.B2) Then
B2 = F.B2
Changes.B2 = F.B2
End If
...
If Not B1000.Equals(F.B1000) Then
B1000 = F.B100
Changes.B1000 = F.B1000
End If
Return Changes
End Function
End Class
As you can see, the same "If Not X.Equals(F.X) ..." has to be copy pasted many many times.
Here is a second example where I am taking the changes calculated before, and using them to update the value of the Foo object.
Sub Update(Changes as Foo)
If Changes.B1.HasValue Then
B1 = Changes.B1
End If
If Changes.B2.HasValue Then
B2 = Changes.B2
End If
...
If Changes.B1000.HasValue Then
B1000 = Changes.B1000
End If
End Sub
This kind of structure isn't something I'm happy with. It's fragile, it's ugly, it's repetitive. Is there something I can do about it?
One option is to pass the 3 members by ref, but the problem is you still have these 3 calls to the same variable, which is still repeditive:
Public Shared Sub CheckChange(Of T)(ByRef OldV As T, NewV As T, ByRef Change As T)
If Not OldV.Equals(NewV) Then
OldV = NewV
Change = NewV
End If
End Sub
Function GetChanges(F as Foo) As Foo
Dim Changes As New Foo()
CheckChange(B1, F.B1, Changes.B1)
CheckChange(B2, F.B2, Changes.B2)
...
CheckChange(B1000, F.B1000, Changes.B1000)
return Changes
End Function
It would be great if I could do something like:
Sub CheckChanges(F as Foo, ByRef Changes as Foo, Member as ???)
If Not Member.Equals(F.Member) Then
Member = F.Member
Changes.Member = F.Member
End If
End Sub
Function GetChanges(F as Foo) As Foo
Dim Changes As New Foo()
CheckChange(F, Changes, B1)
CheckChange(F, Changes, B2)
...
CheckChange(F, Changes, B1000)
return Changes
End Function
I'm not even sure what that kind of feature would be called. Relative member referencing? Anyway, is there any kind of feature (this, or otherwise) that would make the code more robust when having to run the same logic on multiple members?
On the subject of the question, code in situations like this can often be simplified by using a bit of Reflection. I am yet to test this but I think that this method should work:
Private Sub UpdateIfNotEqual(source As Foo, changes As Foo, fieldName As String)
Dim fooType = GetType(Foo)
Dim field = fooType.GetField(fieldName)
Dim currentValue = field.GetValue(Me)
Dim sourceValue = field.GetValue(source)
If Not currentValue.Equals(sourceValue) Then
field.SetValue(Me, sourceValue)
field.SetValue(changes, sourceValue)
End If
End Sub
You'd call it like so:
UpdateIfNotEqual(F, Changes, NameOf(B1))
UpdateIfNotEqual(F, Changes, NameOf(B2))
'...
UpdateIfNotEqual(F, Changes, NameOf(B1000))
If the fields really were numbered then you could even use a loop:
For i = 1 To 1000
UpdateIfNotEqual(F, Changes, $"B{i}")
Next
The drawback is that Reflection is very slow. It may not be a big deal for this much data but only testing would tell you for sure.
Reflection
Class Foo
Public B1 As Boolean
Public B2 As Boolean
Public B3 As Boolean
Public C1 As Integer = Rnd() * 100
Function GetChanges(F As Foo) As Foo
Dim Changes As New Foo()
For Each field As System.Reflection.FieldInfo In F.GetType.GetFields.Where(Function(x) x.Name.StartsWith("B"))
If field.GetValue(F) <> Me.GetType.GetField(field.Name).GetValue(F) Then
Changes.GetType.GetField(field.Name).SetValue(Changes, field.GetValue(F))
Else
'????????
End If
Next
Return Changes
End Function
End Class
PropertyDescriptor
Imports System.ComponentModel
Class Foo
Property B1 As Integer
Property B2 As Boolean
Property Text As String
Function GetChanges(F As Foo) As Foo
Dim Changes As New Foo()
Dim pdc As PropertyDescriptorCollection = TypeDescriptor.GetProperties(F)
For Each pd As PropertyDescriptor In pdc
If pd.IsReadOnly = False Then
If pd.IsBrowsable = True Then
'add more conditions if needed
Changes.GetType.GetProperty(pd.Name).SetValue(Changes, pd.GetValue(F), Nothing)
End If
End If
Next
Return Changes
End Function
End Class
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.
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.
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.