Best way to instantiate a class when the class name is passed as variable?

B

Brian

I have many similar classes in a project, one for each type of report
my app can create. I want to instantiate them based on a value passed
in by a scheduler module.

Right now I have

Sub RunReports(sReport)
Select Case sReport
Case "CRByDistrict"
oReport = New CRByDistrict

Case "CRCareConversionRate"
oReport = New CRCareConversionRate

Case (etc.....)
End Select

oReport.Build ' All classes implement a Build method
End Sub

For example, if the scheduler says it's time to run the "CRByDistrict"
report I want to pass in "CRByDistrict" and invoke

oReport = New <className> ' One line handles all reports

where in this case <className> is "CRByDistrict". I've looked at
Activator.CreateInstance and some other things, but I can't figure out
the best, simplest way to do this.

I'm tired of maintaining what has become a huge Select Case statement
each time I add a new report (class).

Thanks,
Brian
 
T

Tom Shelton

I have many similar classes in a project, one for each type of report
my app can create. I want to instantiate them based on a value passed
in by a scheduler module.

Right now I have

Sub RunReports(sReport)
Select Case sReport
Case "CRByDistrict"
oReport = New CRByDistrict

Case "CRCareConversionRate"
oReport = New CRCareConversionRate

Case (etc.....)
End Select

oReport.Build ' All classes implement a Build method
End Sub

For example, if the scheduler says it's time to run the "CRByDistrict"
report I want to pass in "CRByDistrict" and invoke

oReport = New <className> ' One line handles all reports

where in this case <className> is "CRByDistrict". I've looked at
Activator.CreateInstance and some other things, but I can't figure out
the best, simplest way to do this.

I'm tired of maintaining what has become a huge Select Case statement
each time I add a new report (class).

Thanks,
Brian

I am assuming that all of the report classes implement a specific
interface or inherit from a common base class - but for this example
I'll assume an interface named IReport that has the Build method defined.
Given the above, something like this should work:

Private Sub RunReport (ByVal ReportType As String)
Dim report As IReport = DirectCast _
(Activator.CreateInstance (Type.GetType (ReportType)), _
IReport)

report.Build()

End Sub
 
I

Imran Koradia

IMHO, Activactor.CreateInstance should be the way to go for you. Since you
have all your classes in the same project (and hence the same assembly), it
should be pretty simple to use this method. Something like:

dim reportType = Type.GetType(sReport)
dim reportObject = Activator.CreateInstance(reportType)

Or

dim reportObject = Activator.CreateInstance(Nothing, sReport)

hope that helps..
Imran.
 
I

Imran Koradia

oops..that wouldn't even compile ;-)

I meant:

dim reportType As Type = Type.GetType(sReport)
dim reportObject As Object = Activator.CreateInstance(reportType)

Or

dim reportObject as Object = Activator.CreateInstance(Nothing, sReport)
 
B

Brian Fenske

I guess I'm missing something fundamental here. I tried your (very helpful)
suggestion, but Type.GetType(sReport) returns Nothing in my case.

Should I be doing something in my class definitions to set their Type? I
don't have a base class.

Thanks,
Brian
 
I

Imran Koradia

Sorry - I should have been more complete. If your class is under a
namespace, you'll need to provide the namespace information to the GetType
method as well. By default, VB .NET uses the application name as the default
namespace which is why you would need to do this. You can check the default
namespace from Project --> <your project> Properties -- > Root Namespace
field. You can change that to whatever you like or you can even wipe it out
altogether. In general, you'll need to provide the entire namespace
hierarchy. For example:

If your project name is "myProject", by default, your root namespace will be
"myProject". (For project names with spaces, I believe VB .NET uses and
underscore instead of the spaces; so "My Project" becomes "My_Project").
Suppose your report class is something like:

NameSpace AllReports
....
....
Class CRByDistrict
Public Sub Build( )

End Sub
End Class

Class CRCareConversionRate
Public Sub Build( )

End Sub
End Class
....
....
End NameSpace

Then you would do:

Sub RunReports(sReport)
Dim reportType As Type = _
Type.GetType("myProject." & "AllReports." & sReport)
Dim reportObject As Object = _
Activator.CreateInstance(myType)
reportType.InvokeMember("Build", _
Reflection.BindingFlags.InvokeMethod, _
Nothing, reportObject, Nothing)
End Sub

Ofcourse, if you have no namespaces and if you are going to wipe out the
default namespace as well, then you can directly use the report class name
in the Type.GetType method. Thats upto how you would want it.

hope that helps..
Imran.
 
D

David

Sorry - I should have been more complete. If your class is under a
namespace, you'll need to provide the namespace information to the GetType
method as well.

It's worth adding that Type.GetType is more than a bit flaky. It works
well with the standard assemblies, usually works inside a single
assembly application, but once you get into multiple assemblies all bets
are off.

In all honesty, I've never figured out the pattern to when it works or
not. Sometimes it works even across assemblies, and sometimes it
doesn't (even using the AssemblyQualifiedName doesn't help in this
situation). I'd be very interested if somebody has spent the time to
track this down, since it's a very useful function.

Personally, I'd be very wary of using it in the situation in this
thread, since with this type of system the logical next step is a plugin
system where you load new report types without recompiling, and
Type.GetType is highly unlikely to work reliably in that situation.
You're probably better off looping through known assemblies looking for
a specific interface (do it once at startup then stash away the types
you find). It's a bit more code right now, but you gain a lot in
robustness and expandability.
 
I

Imran Koradia

Hmm. I haven't had problems with the GetType method as such but honestly,
I've not used it so extensively either to be a good judge of its
reliability. Brian - if you think thats going to be a problem, here's
another way to accomplish the same thing:

Imports System.Reflection

Dim reportObject As Object = _
[Assembly].GetExecutingAssembly.CreateInstance("myProject" & sReport)
CallByName(reportObject, "Build", CallType.Method, Nothing)

Imran.
 
H

Herfried K. Wagner [MVP]

* (e-mail address removed) (Brian) scripsit:
I have many similar classes in a project, one for each type of report
my app can create. I want to instantiate them based on a value passed
in by a scheduler module.

\\\
Dim frm As Form = _
DirectCast( _
Activator.CreateInstance( _
Type.GetType("MyApplication.SampleForm") _
), _
Form _
)
frm.Show()
///

More general:

\\\
Private Function CreateClassByName( _
ByVal PartialAssemblyName As String, _
ByVal QualifiedClassName As String _
) As Object
Return _
Activator.CreateInstance( _
[Assembly].LoadWithPartialName( _
PartialAssemblyName _
).GetType(QualifiedClassName) _
)
End Function
///

Usage:

\\\
Dim c As Control = _
DirectCast( _
CreateClassByName( _
"System.Windows.Forms", _
"System.Windows.Forms.Button" _
), _
Control _
)
With c
.Location = New Point(10, 10)
.Size = New Size(80, 26)
.Text = "Hello World"
End With
Me.Controls.Add(c)
///
 
T

Tom Shelton

It's worth adding that Type.GetType is more than a bit flaky. It works
well with the standard assemblies, usually works inside a single
assembly application, but once you get into multiple assemblies all bets
are off.

Never had a lick of trouble with it myself....
 
B

Brian Fenske

Well, after trying many different ways to get back a Type, I gave up on
GetType.

I used Object Browser to note that my Namespace is indeed the name of my
project as well as the executable (which must be the defaults for a VB
project since I didn't attempt to change any of them when I ran the project
wizard): CRMReportGenerator

So I tried the following, all of which return Nothing:

Type.GetType(sReport)
Type.GetType("CRMReportGenerator." & sReport)
Type.GetType("CRMReportGenerator.CRMReportGenerator." & sReport)
Type.GetType("DSR Report Generator.CRMReportGenerator." & sReport) ' Here I
tried the Assembly name found in Assembly.vb

Maybe there is a combo I didn't try that works.

Anyway, here is what did work:

Imports System.Reflection

Dim oReport as Object
oReport =
[Assembly].GetExecutingAssembly.CreateInstance("CRMReportGenerator." &
sReport)
oReport.Build

Short and sweet.

Thank you all for your help!

Brian



Herfried K. Wagner said:
* (e-mail address removed) (Brian) scripsit:
I have many similar classes in a project, one for each type of report
my app can create. I want to instantiate them based on a value passed
in by a scheduler module.

\\\
Dim frm As Form = _
DirectCast( _
Activator.CreateInstance( _
Type.GetType("MyApplication.SampleForm") _
), _
Form _
)
frm.Show()
///

More general:

\\\
Private Function CreateClassByName( _
ByVal PartialAssemblyName As String, _
ByVal QualifiedClassName As String _
) As Object
Return _
Activator.CreateInstance( _
[Assembly].LoadWithPartialName( _
PartialAssemblyName _
).GetType(QualifiedClassName) _
)
End Function
///

Usage:

\\\
Dim c As Control = _
DirectCast( _
CreateClassByName( _
"System.Windows.Forms", _
"System.Windows.Forms.Button" _
), _
Control _
)
With c
.Location = New Point(10, 10)
.Size = New Size(80, 26)
.Text = "Hello World"
End With
Me.Controls.Add(c)
///
 
B

Brian Fenske

Oops, I was not entirely correct. My Assembly Title is "DSR Report
Generator". My AssemblyName and Root Namespace are "CRMReportGenerator"

Brian
 
I

Imran Koradia

Anyway, here is what did work:
Imports System.Reflection

Dim oReport as Object
oReport =
[Assembly].GetExecutingAssembly.CreateInstance("CRMReportGenerator." &
sReport)

That's what I suggested in my 3rd post...
oReport.Build

This will only work if you have Option Strict Off. If you have Option Strict
On, you would need to typecast to the actual report type. Since dynamic
casting cannot be accomplished, you'll need to use CallByName to execute the
method as I mentioned in that post.

Imran.
 
T

Tom Shelton

Anyway, here is what did work:

Imports System.Reflection

Dim oReport as Object
oReport =
[Assembly].GetExecutingAssembly.CreateInstance("CRMReportGenerator." &
sReport)

That's what I suggested in my 3rd post...
oReport.Build

This will only work if you have Option Strict Off. If you have Option Strict
On, you would need to typecast to the actual report type. Since dynamic
casting cannot be accomplished, you'll need to use CallByName to execute the
method as I mentioned in that post.

Imran.


Or just make them all implement a specific interface... That way, you
can have early binding....

Dim report As IReport
report = CType (....., IReport)
report.Build()


In fact, that is the way I would probably do this.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top