Is it possible using Reflection or some other method to obtain a reference to a specific class instance from the name of that class instance?
For example the framework for the applications i develop heavily uses public class instances such as:
Public bMyreference as MyReference = new MyReference
Then throughout the application bMyReference is used by custom controls and code.
One of the properties of the custom controls is the "FieldName" which references a Property in these class instances (bMyReference.MyField) as a string.
What i would like to be able to do is analyze this string "bMyReference.MyField" and then refer back to the actual Instance/Property.
In VB6 I would use an EVAL or something simular to convert the string to an actual object but this obviously doesn't work in VB.net
What I'm picturing is something like this
Dim FieldName as String = MyControl.FieldName ' sets FielName to bMyReference.MyField
Dim FieldObject() as String = FieldName.Split(".") ' Split into the Object / Property
Dim myInstance as Object = ......... ' Obtain a reference to the Instance and set as myInstance
Dim myProperty = myInstance.GetType().GetProperty(FieldObject(1))
I don´t know if I´ve understood you well, but my answer is yes, you can do it by reflection. You´ll need to import System.Reflection namespace.
Here is an example:
' Note that I´m in namespace ConsoleApplication1
Dim NameOfMyClass As String = "ConsoleApplication1.MyClassA"
Dim NameOfMyPropertyInMyClass As String = "MyFieldInClassA"
' Note that you are getting a NEW instance of MyClassA
Dim MyInstance As Object = Activator.CreateInstance(Type.GetType(NameOfMyClass))
' A PropertyInfo object will give you access to the value of your desired field
Dim MyProperty As PropertyInfo = MyInstance.GetType().GetProperty(NameOfMyPropertyInMyClass)
Once you have MyProperty, uou can get the value of your property, just like this:
MyProperty.GetValue(MyInstance, Nothing)
Passing to the method the instace of what you want to know the value.
Tell me if this resolve your question, please :-)
EDIT
This would be ClassA.vb
Public Class MyClassA
Private _myFieldInClassA As String
Public Property MyFieldInClassA() As String
Get
Return _myFieldInClassA
End Get
Set(ByVal value As String)
_myFieldInClassA = value
End Set
End Property
End Class
Related
I have a class with several properties.
Public Class test
Public Property a As String
Public Property b As String
Public Property c As String
Public Property d As String
Public Property e As String
Public Property f As String
Public Property g As String
End Class
In my VB.net code, I am assigning a value to each property.
I want to send the whole test class as one parameter, and use all the values inside it.
So that if I add extra parameters later on, I want them to be used dynamically, instead of writing this everytime:
Textbox1.text= test.a & test.b & test.c .......
Any way to do this?
Im not really writing the values in a textbox, but this is just an simplified example.
I think what you want is a property. You'll need to add a property to your class like:
Public Property Combination() As String
Get
Return a & b & c & d & e ...
End Get
End Property
Then to get the value you'd use
Textbox1.text = test.combination
(for more details you can see http://www.dotnetperls.com/property-vbnet)
I recommend you override the built-in ToString function. Also, to further simplify this, add a CType operator.
Public Class test
Public Property a As String
Public Property b As String
Public Property c As String
Public Property d As String
Public Property e As String
Public Property f As String
Public Property g As String
Public Shared Widening Operator CType(obj As test) As String
Return If((obj Is Nothing), Nothing, obj.ToString())
End Operator
Public Overrides Function ToString() As String
Return String.Concat(Me.a, Me.b, Me.c, Me.d, Me.e, Me.f, Me.g)
End Function
End Class
The you could just do:
Textbox1.text = test
There is a way to dynamically get and set the value of properties on any object. Such functionality in .NET is collectively referred to as Reflection. For instance, to loop through all of the properties in an object, you could do something like this:
Public Function GetPropertyValues(o As Object) As String
Dim builder As New StringBuilder()
For Each i As PropertyInfo In o.GetType().GetProperties
Dim value As Object = Nothing
If i.CanRead Then
value = i.GetValue(o)
End If
If value IsNot Nothing Then
builder.Append(value.ToString())
End If
Next
Return builder.ToString()
End Function
In the above example, it calls i.GetValue to get the value of the property, but you can also call i.SetValue to set the value of the property. However, reflection is inefficient and, if used inappropriately, it can lead to brittle code. As such, as a general rule, you should avoid using reflection as long as there is any other better way to do the same thing. In other words, you should typically save reflection as a last resort.
Without more details, it's difficult to say for sure what other options would work well in your particular situation, but I strongly suspect that a better solution would be to use a List or Dictionary, for instance:
Dim myList As New List(Of String)()
myList.Add("first")
myList.Add("second")
myList.Add("third")
' ...
For Each i As String In myList
Textbox1.Text &= i
Next
Or:
Dim myDictionary As New Dictionary(Of String, String)()
myDictionary("a") = "first"
myDictionary("b") = "first"
myDictionary("c") = "first"
' ...
For Each i As KeyValuePair(Of String, String) In myDictionary
Textbox1.Text &= i.Value
Next
I'm trying to teach myself reflection and have been googling but I can't wrap my head around it entirely. I created a class called DataClass which contains a method called GetClassFromDB as you can see below, which will be inherited from multiple classes.
What I am attempting to do is have my dataclass read the TableName property that is defined within objResults. Once I pull in the tablename from objResults I would query the SQL database for a dataset. Once I have the dataset I would create a new object of the same TYPE inheriting this class (Which will be different types) and populate it from the dataset. Once I have the newly populated class I will return it for use.
I believe I have gotten most of the way there properly (Please correct me if there is a better way), but my real question is this. How can I create a new class of the type thats deriving that class from that string name that I getting in my code, or the type. I would want to have all the accessible properties from objResults available.
Namespace MyApp
Public Class DataClass
Private _TableName As String
Private _Name As String
Overridable ReadOnly Property TableName As String
Get
Return _TableName
End Get
End Property
Public Overloads Function GetClassFromDB() As Object
Try
Dim BaseObject As New Object
'Get the object name
Dim objName As String = MyBase.GetType().Name
'Gets the type thats calling this method
Dim objDerived As Type = MyBase.GetType()
'Get the property info to request the tablename from the derived class
Dim TableName As PropertyInfo = objDerived.GetProperty("TableName")
Dim TableNameString As String = TableName.GetValue(Me, Nothing).ToString
'Once I get the table name from objResults I can perform the SQL
Dim QueryResults as DataSet = SQLiteCLass.Query("Select * FROM TableNameString")
'Once I get data from the SQL I want to create a new object of the type deriving this method.
'In this example is objResults
Dim NewObject as objDerived
'Now I can fill my new object with the results and return it as an object
'THIS IS MY QUESTION - How can I create a new object of the TYPE that I receive from Reflection
Return False
Catch ex As Exception
Return False
End Try
End Function
End Class
End Namespace
and this is a sample class that would inherit my dataclass.
Public Class objResults
Inherits MyApp.DataClass
Private _GameID As Guid
Public Property GameID As Guid
Get
Return _GameID
End Get
Set(ByVal value As Guid)
_GameID = value
End Set
End Property
Public Overrides ReadOnly Property TableName As String
Get
Return "This is my tablename"
End Get
End Property
End Class
and this is how I would use this in code.
Dim objResult as New objResults
Dim TodaysResult as objResultsCollection
TodaysResult = objResult.GetClassFromDB()
I am trying to add rows to a public class that has public fields and am getting an error: Object reference not set to an instance of an object
Public Class EmailRecipient
EmailAddress As String = ""
FullName As String = ""
End Class
Public Class EmailDetails
Public FromEmail As String = ""
Public ToEmails As List(Of Emails) = nothing
End Class
Public Sub SetEmailDetails
'Populate EmailRecipient Class
Dim er As New EmailRecipient
er.EmailAddress = "rodney#norespect.com"
er.FullName = "Rodney Dangerfield"
'Populate EmailDetails Class
Dim ed As New EmailDetails
ed.FromEmail = "sender#danger.com" 'This works fine
ed.ToEmails.Add(er) 'Here error happens
End Sub
I'm guessing I need to create an instance of the EmailRecipient class before I can add an item to it.
Not sure how to do that with a Public Field in a Public Class??
It's been a rough day. I got up this morning, put a shirt on and a button fell off. I picked up my briefcase and the handle came off. I'm afraid to go to the bathroom.
Thanks for the help :-)
I'm guessing I need to create an instance of the EmailRecipient class
You already have an instance of the EmailRecipient class. That's your er variable. You actually have two errors here. First, you explicitly set ToEmails to Nothing:
Public ToEmails As List(Of Emails) = nothing
This means that your ToEmails variable is a Null Reference. It doesn't have an actual object yet.
The second issue is that you shouldn't get that excpetion, because this shouldn't even compile. You define ToEmails as a List(Of Emails), but tried to add an object of type "EmailRecepient" to it. That should be a compiler error. If it's not, you need to turn Option Strict or Option Infer back on.
So what you really need is an instance of a the List(Of EmailRecipient) type. Fix the bad line of code like this:
Public ToEmails As New List(Of EmailRecipient)
I have a requirement where I need to have a "type safe" way of accessing property names, without actually instantiating an object to get to the property. To give an example, consider a method that takes as arguments a list of IMyObject and a string that represents a property name (a property that exists in IMyObject).
The methods implementation will take the list and access all the objects in the list using the property name passed... for some reason or another, we won't dwell on that!!
Now, I know that you can do this using an instantiated object, something like ...
Dim x as MyObject = nothing
Dim prop As PropertyInfo = PropHelper.GetProperty(Of MyObject)(Function() x.MyProperty)
Where my helper method uses reflection to get the name of the property as a string - there are numerous examples of this flying around on the web!
But I don't want to have to create this pointless object, I just want to do something like MyObject.MyProperty! Reflection allows you to iterate through a types properties and methods without declaring an object of that type... but I want to access a specific property and retrieve the string version of its name without iteration and without declaring an object of that type!
The main point here is that although I am trying to get the property name as a string... this is done at run time... at compile time, I want this to be type safe so if someone changes the property name, the compilation will break.
Can anyone help in this quest!?!
So here is a quick code-listing to demonstrate the answer that I was looking for:
Imports System.Linq.Expressions
Public Class A
Public Prop1 As String
Public Prop2 As Integer
End Class
Public Class Form1
Public Function GetPropertyNameB(Of TModel, TProperty)(ByVal [property] As Expression(Of Func(Of TModel, TProperty))) As String
Dim memberExpression As MemberExpression = DirectCast([property].Body, MemberExpression)
Return memberExpression.Member.Name
End Function
Public Sub New()
InitializeComponent()
Dim propertyName As String = GetPropertyNameB(Function(myObj As A) myObj.Prop1)
Dim propertyName2 As String = GetPropertyNameB(Function(myObj As A) myObj.Prop2)
MsgBox(propertyName & " | " & propertyName2)
End
End Sub
End Class
You may be able to pass the property in as a simple lamdba expression, and take it in the method as an expression tree. You should be able to analyze the expression tree to get the string name of the property, but it the lambda expression will fail to compile if the property name changes. Check out this page for more details:
http://msdn.microsoft.com/en-us/library/bb397951.aspx
You can make use of the NameOf function:
Dim fieldName = nameOf(MyClass.MyField)
I've inherited some code and it is making me cringe when I look at it. Is there more elegant way to write the following?
Dim myItem As DTO.MyBaseClass = Nothing
Dim myType As String = GetTypeString()
Select Case myType
Case Is = "Case1"
myItem = Bus.BusManager(Of DTO.MyClass1).Read()
Case Is = "Case2"
myItem = Bus.BusManager(Of DTO.MyClass2).Read()
'... etc etc for 30 lines
Is there a way to make a map from the string to the class type and then just have a line like so? Or something similar?
myItem = Bus.BusManager(Of MappingDealy(myType)).Read()
Since BusManager is a Generic, the type you pass into Of <type> must be specified at compile time. It's not like a traditional parameter that you can change at runtime.
It's unclear from the code you listed what BusManager actually does. If all it's doing is creating an instance of the Generic type, then maybe the person who created it doesn't really understand generics. Do you have the ability to rework how BusManager works, or are you limited to using it as is?
As #jmoreno mentioned, you can use reflection to create an instance of a type from a string containting the name of the type. Here's how that would work:
Imports System.Reflection
Imports System.IO
Public Class ObjectFactory
Private Shared Function CreateObjectFromAssembly(ByVal assembly As Assembly, ByVal typeName As String) As Object
' resolve the type
Dim targetType As Type = assembly.GetType(typeName)
If targetType Is Nothing Then
Throw New ArgumentException("Can't load type " + typeName)
End If
' get the default constructor and instantiate
Dim types(-1) As Type
Dim info As ConstructorInfo = targetType.GetConstructor(types)
Dim targetObject As Object = info.Invoke(Nothing)
If targetObject Is Nothing Then
Throw New ArgumentException("Can't instantiate type " + typeName)
End If
Return targetObject
End Function
Public Shared Function CreateObject(ByVal typeName As String) As Object
Return CreateObjectFromAssembly(Assembly.GetExecutingAssembly, typeName)
End Function
Public Shared Function CreateObject(ByVal typeName As String, ByVal assemblyFileName As String) As Object
Dim assemblyFileInfo = New FileInfo(assemblyFileName)
If assemblyFileInfo.Exists Then
Return CreateObjectFromAssembly(Reflection.Assembly.LoadFrom(assemblyFileName), typeName)
Else
Throw New ArgumentException(assemblyFileName + " cannot be found.")
End If
End Function
End Class
In a production app, I'd probably set the return type for all of these methods to my base class or interface. Just make sure you pass in the full typeName including the Namespace.
With that factory class in place, then the elegant version of your code would look something like this:
Dim myItem as DTO.MyBaseClass = ObjectFactory.CreateObject("DTO." & GetTypeString())
First of all, never use Case Is = and never initialize to Nothing. So a quick one would be:
Dim myItem As DTO.MyBaseClass
Select Case GetTypeString()
Case "Case1"
myItem = Bus.BusManager(Of DTO.MyClass1).Read()
' etc etc
But since you're using templating, there's really no way to map it unless you want to use reflection, which is horribly inefficient at the expense of slightly cleaner and shorter code. You could also add an Imports DTO to save on 124 characters, and also Bus to save on another 120 characters.
Without seeing more of the code I would recommend use an Enumeration on my Case Statement to prevent minor bugs from appearing.
You could then either use a Factory Method to process the data based on the Enumeration.