How to get qualifiers for a class - vb.net

I have the following code to create an instance of one of my classes based on the class name.
'Get a System.Type corresponding to the name of the parent class
Dim tt As Type = Type.GetType(item.ParentClass)
'Create an instance of the specified type using the types default constructor
Dim theObject As Object = Activator.CreateInstance(tt)
This works fine if I have the full class as in MainClassName.Namespace.ClassName
But if I try to do this with just ClassName, tt equals to nothing.
Is there a way to use ClassName and get the fully qualified name as in MainClassName.Namespace.ClassName

Is there a way to use ClassName and get the fully qualified name as in MainClassName.Namespace.ClassName
No. This cannot logically work. Consider the following case: you have two classes, Foo.X and Bar.X. Just specifying X is therefore ambiguous. Indeed that’s the motivation behind namespaces and fully qualified names in the first place.
Even if it were possible (for instance by returning a list of candidate types) this would be impractical since it would require the runtime to walk through the whole list of classes that are loaded by all referenced assemblies. This can be a huge number, even with just the basic framework loaded. GetType would be prohibitively inefficient.

You need to use Type.AssemblyQualifiedName, like this:
'part 1 of your code - store class name in a universal format
Dim className As String = GetType(item.ParentClass).AssemblyQualifiedName
'part 2 of your code - use a previously stored class name to create instance
Dim tt As Type = Type.GetType(className)
Dim theObject As Object = Activator.CreateInstance(tt)
Credit goes to this answer on SO.

Related

Structure where the data type of a member differs

Maybe superfluous, but some intro...
I am rewriting an add-in for my CAD-application (using VB.NET).
This add-in reads, via an API, a bunch of metadata from a file, presents it in a Form. This data can then be (partially) changed and written back to the file.
This metadata is accessible in a consistent way, however the data type is not the same everywhere (String, Currency, Date, Boolean, Long and IPictureDisp).
Currently I have a much too complex class with several arrays. I thought it might be smarter to create a structure. The problem is the varying data type.
Is it possible to define a structure with a member with varying datat type, or am I forced to define a different structure for each data type?
You have a few options...
1: Use Object
Nice and simple, every data type inherits from Object - so if your struct contains a property of type Object, you can put pretty much any data type in there
From the docs:
The Object data type can point to data of any data type, including any object instance your application recognizes. Use Object when you do not know at compile time what data type the variable might point to.
However, this does mean that you will get next to no help from the compiler when you are trying to write code using this property. You will also probably have to cast any time you need to do anything type-specific
2: Generic Types
This will not fit situations where you are not sure of the type. You can create a generic struct using the Of syntax.
You'd create it as so:
Structure MyStructure(Of T)
'our changing type
Dim MyCustomData As T
'...alongside regular types
Dim Name As String
Dim OtherThing As Integer
End Structure
and then when you need to create the structure, you'd simply pass the type in and assign the value
Dim struct As New MyStructure(Of Integer)
struct.MyCustomData = 123
Dim struct2 As New MyStructure(Of String)
struct2.MyCustomData = "a"

Type.GetType fails even though class exists

I have a text file that contains different types of data on each row. I'm trying to read the file and create a different (sub)class instance for each one. So I did...
Dim t As Type = Type.GetType("DtaRow")
And this returns Nothing. This is odd, because the very next bit of code in the same file/namespace/everything is...
Public Class DtaRow...
In fact, the code I'm typing formerly just called it's New method without problem. So maybe I need to do this...
System.Reflection.Assembly.GetExecutingAssembly().GetType("DtaRow")
Nope, that's Nothing as well. So then I followed some instructions I found here about looping over the Assemblies to look in all of them... no luck there either. So then I try looking for "Int32" and a bunch of others, phail.
Can someone tell me the obvious thing I'm doing wrong here?
Since a single Assembly can have multiple Namespaces and a type of the same name can exist in more that one NameSpace, you have to qualify the type name with the Namespace. From MSDN:
If the type is in the currently executing assembly or in Mscorlib.dll, it is sufficient to supply the type name qualified by its namespace.
' should work
myT = Type.GetType("MyNameSpace.DtaRow")
' similarly:
myT = Type.GetType("System.Int32")
The NameSpace is integral to the Type name and NET does much the same:
Dim a As New Animal
myT = a.GetType()
Console.WriteLine(myT.ToString())
' => MyApplication1.Animal
If the code is in a form or class in the same namespace, Me.GetType.Namespace will get you the string name you can prepend; or you can use a dummy class. However, the mention of using the code in/as an extension might make that problematic. First Me will not be legal and using a dummy type will report the NameSpace for the dummy object.
If there is only one NameSpace to deal with, just use a variable and set it once:
Friend myNameSpace As String
...
' elsewhere something like a MainForm sets it:
myNameSpace - Me.GetType().NameSpace & "."
Then, just prepend that to the type names you load.
For more than one involved NameSpace, I'd use a Dictionary(Of String, String) using the type name as the key and NameSpace qualified string as the value:
myTypeName.Add("DtaRow", "FooNameSpace.DtaRow")
myTypeName.Add("Animal", "CircusActs.Animal")

Comparing String to class name

I have a little Problem:
I have a method that parses an incoming string for certain values, if a value is found, a new class is instantiated. The class name is identical to the parsed string. At the moment, my code looks like this:
Public Class Test1
End Class
Public Class Important
End Class
Public Class DoWork
Public Sub DoWork(incoming as String)
Select case incoming
case "Test1"
dim myobj as new Test1
Case "Important"
dim myobj as new Important
End Select
End Sub
End Class
I do not like the string literals like "Test1" - i could store them in a constant, but if the class names change, they have to be changed too. Is there a way to replace the literals with the Name of class?
I know that me.gettype produces the result for instantiated objects, but what about the simple name for a class, which is no object at this moment?
If your string is in correct format you can use Type.GetType(string) method to retrieve type. Then you can use Activator class to create instance if you have default constructor on that type.
Rafal's answer is good if you're stuck with the current situation, with the incoming string parameter. But it's still a bit fragile. What if the incoming parameter changes? What if you want to restructure your code, moving some classes to different namespaces or assemblies? What if those strings change - do you now have to rename your classes and recompile? You don't see the magic strings explicitly now, but they're still there.
So ask yourself - where are those strings coming from? Are they generated internally by your code? If so, you might want to generate, instead of strings, an Enum value that corresponds to the class to be instantiated. If they're external strings that you map to your classes, consider having explicit mapping (in a configuration file, for instance) that map String->Type. It's a bit more cumbersome, but a lot more flexible.

Structs in collections

I would like to store references to a bunch of structs in a collection. The general scaffolding looks like this:
Structure myStructType
Dim prop1 as String
Dim prop2 as int
End Structure
Dim myList as new List(Of myStructType)()
'Wrongness below
Dim myStruct as new myStructType()
myStruct.prop1 = "struct1"
myStruct.prop2 = 1
myList.Add(myStruct)
myStruct = new myStructType()
mystruct.prop1 = "number two"
mystruct.prop2 = 2
myList.Add(myStruct)
now this doesn't work, because it's referencing the same memory. What I would really want is the 'pass reference by value' behaviour that is also used for reference types, so that I can easily keep producing more of them.
Is there any way to fix this other than to make the structs into classes? Is this actually a proper way to use structs, or do I have it all wrong?
This code does the same thing whether it is a struct or a class because you are invoking new myStructType() for each object. That being said, be aware that later retrieving and modifiying those myStructType objects behave differently. If it is derrived froma structure then you are copying the data on a retrieve, leaving the original untouched in the list. If it is derrived from a class then you are getting a reference to that object and changes made using that reference change the instance in the list.
I still wonder what you are trying to accomplish (or avoid) by using structures instead of classes?

Create a "clone" of this object, not point to it

Let's say I got a list called
myFirstList
And then I want to create a copy of that list so I can do some tweaks of my own. So I do this:
mySecondList = myFirstList
mySecondList.doTweaks
But I noticed that the tweaks also affect the myFirstList object! I only want the tweaks to affect the second one...
And afterwards I will want to completely delete mySecondList, so I do mySecondList = Nothing and I'm good, right?
Adam Rackis, I don't like your "Of course it does", because it is not at all obvious.
If you have a string variable that you assign to another string variabe, you do not change them both when making changes to one of them. They do not point to the same physical piece of memory, so why is it obvious that classes do?
Also, the thing is not even consistent. In the following case, you will have all elements in the array pointing at the same object (they all end up with the variable Number set to 10:
SourceObject = New SomeClass
For i = 1 To 10
SourceObject.Number = i
ObjectArray.Add = SourceObject
Next i
BUT, the following will give you 10 different instances:
For i = 1 To 10
SourceObject = New SomeClass
SourceObject.Number = i
ObjectArray.Add = SourceObject
Next i
Apparently the scope of the object makes a difference, so it is not at all obvious what happens.
Here is how you do it:
'copy one object to another via reflection properties
For Each p As System.Reflection.PropertyInfo In originalobject.GetType().GetProperties()
If p.CanRead Then
clone.GetType().GetProperty(p.Name).SetValue(clone, p.GetValue(OriginalObject, Nothing))
End If
Next
in some cases when the clone object got read-only properties you need to check that first.
For Each p As System.Reflection.PropertyInfo In originalobject.GetType().GetProperties()
If p.CanRead AndAlso clone.GetType().GetProperty(p.Name).CanWrite Then
clone.GetType().GetProperty(p.Name).SetValue(clone, p.GetValue(OriginalObject, Nothing))
End If
Next
Since you have not divulged the type of item that you are storing n your list, I assume it's something that's implementing IClonable (Otherwise, if you can, implement IClonable, or figure out a way to clone individual item in the list).
Try something like this
mySecondList = myFirstList.[Select](Function(i) i.Clone()).ToList()
But I noticed that the tweaks also
affect the myFirstList object! I only
want the tweaks to affect the second
one...
Of course it does. Both variables are pointing to the same object in memory. Anything you do to the one, happens to the other.
You're going to need to do either a deep clone, or a shallow one, depending on your requirements. This article should give you a better idea what you need to do
Expanding on Adam Rackies' answer I was able to implement the following code using VB.NET.
My goal was to copy a list of objects that served mainly as data transfer objects (i.e. database data). The first the class dtoNamedClass is defined and ShallowCopy method is added. A new variable named dtoNamedClassCloneVar is created and a LINQ select query is used to copy the object variable dtoNamedClassVar.
I was able to make changes to dtoNamedClassCloneVar without affecting dtoNamedClassVar.
Public Class dtoNamedClass
... Custom dto Property Definitions
Public Function ShallowCopy() As dtoNamedClass
Return DirectCast(Me.MemberwiseClone(), dtoNamedClass)
End Function
End Class
Dim dtoNamedClassVar As List(Of dtoNamedClass) = {get your database data}
Dim dtoNamedClassCloneVar =
(From d In Me.dtoNamedClass
Where {add clause if necessary}
Select d.ShallowCopy()).ToList
Here's an additional approach that some may prefer since System.Reflection can be slow.
You'll need to add the Newtonsoft.Json NuGet package to your solution, then:
Imports Newtonsoft.Json
And given a class type of MyClass, cloning can be as easy as:
Dim original as New MyClass
'populate properties of original...
Dim copy as New MyClass
copy = JsonConvert.DeserializeObject(Of MyClass)(JsonConvert.SerializeObject(original))
So the approach is to first use the JSON converter to serialize the original object, and than take that serialized data and deserialize it - specifying the class type - into the class instance copy.
The JSON converters are extremely powerful and flexible; you can do all sorts of custom property mappings and manipulations if you need something the basic approach above doesn't seem to address.
this works for me:
mySecondList = myFirstList.ToList
clone is the object you are attempting to clone to.
dim clone as new YourObjectType
You declare it like that.