DirectCast One derived Collection into base Collection - vb.net

I'm working with old VB 6.0 code that in now in VB.NET. So it is using some obsolete collection types. I'm trying to upgrade these as much as possible without breaking stuff.
Say I have a collection of Books HashSet(Of Book) and I have a collection of Premium Books HashSet(Of PremiumBook).
PremiumBook is derived from Book. The only difference is that I override the EQUALS and HASHCODE methods. Everything else is the same.
Since PremiumBook is a Book I can do:
Dim anyBook as Book
Dim goldBook as PremiumBook = New PremiumBook()
anyBook = goldBook
So why can't I do
DirectCast(HashSet(Of PremiumBook), HashSet(Of Book))
The error that I'm getting is:
"Value of type System.Collections.Generic.HashSet(Of SameNamespace.Different.frmBookManager.PremiumBook)' cannot be converted to 'System.Collections.Generic.HashSet(Of SameNamespace.Something.Book)'.
Is it because the namespaces are different? That doesn't make any sense to me.
I feel as if i have a collection of objects, any derived type should be able to fit in that collection.
Thanks!

You can not cast two different object type collections even if they are inherited, but you can cast the individual members to the inherited class Like This example:
Option Strict On
Option Explicit On
Module Module1
Sub Main()
Dim Booklist As List(Of Book) = New List(Of Book)
Booklist.Add(New PremiumBook())
Booklist.Add(New PremiumBook())
Booklist.Add(New PremiumBook())
Booklist.Add(New Book())
For Each bk As Book In Booklist
If bk.GetType() Is GetType(PremiumBook) Then 'If your collection contains multiple types, if not this check can be omitted
Dim premiumBk As PremiumBook = DirectCast(bk, PremiumBook)
End If
Next
Dim premiumBk2 As PremiumBook = DirectCast(Booklist(2), PremiumBook)
End Sub
End Module

Related

'Public member 'Find' on type 'MongoCollectionImpl(Of BsonDocument)' not found.'

I am trying to find a particular user from a mongodb collection that matches the given id. Folliwng is my VB.Net code. However, I keep getting the error 'Public member 'Find' on type 'MongoCollectionImpl(Of BsonDocument)' not found.'
Public Function GetCollectionByName(ByVal collectionName As String)
Dim db As IMongoDatabase = DBcontext()
Dim collection As IMongoCollection(Of BsonDocument)
collection = db.GetCollection(Of BsonDocument)(collectionName)
Return collection
End Function
Public Function GetUser(ByVal id As String)
Dim filter = Builders(Of BsonDocument).Filter.Eq(Of String)("ID", id)
Dim collection = GetCollectionByName("Users")
Dim list = collection.Find(filter).ToList()`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ERROR here
Return list
End Function
Firstly, you must have Option Strict Off for that code to even compile. That's bad. You should immediately turn Option Strict On in the project properties and address all the issues it raises. One of those will be the fact that your GetCollectionByName has no return type declared. That means that here:
Dim list = collection.Find(filter).ToList()
that collection variable is implicitly type Object and you are relying on late binding when calling that Find method because the Object class has no such method. As a result, you get no help from Intellisense and Intellisense would have told you what members were and were not available if you were doing this properly.
Regardless, you still could have made it work if you had actually read the documentation for the types you're using to see what members they have. Here is the documentation for the interface you're using in that GetCollectionByName method and, I don't know about you but I don't see any Find method listed there. There is a FindSync method, so maybe that's what you actually want. If you had Option Strict On and used proper types every where, Intellisense would have shown you that.
You should also turn Option Strict On in the IDE options, so that it is On for all future projects.
I had a look at some documentation for the MongoCollectionImpl for Java and there appears to be a find method there but that doesn't necessarily mean that the same method is available in .NET and you aren't working directly with that class anyway. You are working with the IMongoCollection so you should only be working with members of that interface. Basically, your code would need to look more like the below with Option Strict On:
Public Function GetCollectionByName(ByVal collectionName As String) As IMongoCollection(Of BsonDocument)
Dim db As IMongoDatabase = DBcontext()
Dim collection As IMongoCollection(Of BsonDocument)
collection = db.GetCollection(Of BsonDocument)(collectionName)
Return collection
End Function
Public Function GetUser(ByVal id As String) As List(Of BsonDocument)
Dim filter = Builders(Of BsonDocument).Filter.Eq(Of String)("ID", id)
Dim collection = GetCollectionByName("Users")
Dim list = collection.FindSync(filter).ToList()
Return list
End Function
You may want to declare the GetUser method as type IList(Of BsonDocument) if you want to work with interfaces. You probably ought to rename that method or change the implementation too. If a method is returning a list then the name should not indicate that it returns a single item.

Compile error: Only user-defined types defined in public object modules can be coerced to or from a variant or passed to late-bound functions

I'm struggling with a little bit of VBa and Excel. I need to create a structure in VBa, which is a Type. The problem I have is, I get an error message when I try to execute the code! I feel I need to explain how I have arrived where I am in case I've made an error.
I have read that to create a type, it needs to be made public. As such I created a new Class (under Class Modules). In Class1, I wrote
Public Type SpiderKeyPair
IsComplete As Boolean
Key As String
End Type
And within ThisWorkbook I have the following
Public Sub Test()
Dim skp As SpiderKeyPair
skp.IsComplete = True
skp.Key = "abc"
End Sub
There is no other code. The issue I have is I get the error message
Cannot define a public user-defined type within an object module
If I make the type private I don't get that error, but of course I can't access any of the type's properties (to use .NET terminology).
If I move the code from Class1 into Module1 it works, but, I need to store this into a collection and this is where it's gone wrong and where I am stuck.
I've updated my Test to
Private m_spiderKeys As Collection
Public Sub Test()
Dim sKey As SpiderKeyPair
sKey.IsComplete = False
sKey.Key = "abc"
m_spiderKeys.Add (sKey) 'FAILS HERE
End Sub
Only user-defined types defined in public object modules can be coerced to or from a variant or passed to late-bound functions
I have looked into this but I don't understand what it is I need to do... How do I add the SpiderKeyPair to my collection?
Had the exact same problem and wasted a lot of time because the error information is misleading. I miss having List<>.
In Visual Basic you can't really treat everything as an object. You have Structures and Classes which have a difference at memory allocation: https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/data-types/structures-and-classes
A Type is a structure (so are Arrays), so you if you want a "List" of them you better use an Array and all that comes with it.
If you want to use a Collection to store a "List", you need to create a Class for the object to be handled.
Not amazing... but it is what the language has available.
You seem to be missing basics of OOP or mistaking VBA and VB.NET. Or I do not understand what are you trying to do. Anyhow, try the following:
In a module write this:
Option Explicit
Public Sub Test()
Dim skpObj As SpiderKeyPair
Dim m_spiderKeys As New Collection
Dim lngCounter As Long
For lngCounter = 1 To 4
Set skpObj = New SpiderKeyPair
skpObj.Key = "test" & lngCounter
skpObj.IsComplete = CBool(lngCounter Mod 2 = 0)
m_spiderKeys.Add skpObj
Next lngCounter
For Each skpObj In m_spiderKeys
Debug.Print "-----------------"
Debug.Print skpObj.IsComplete
Debug.Print skpObj.Key
Debug.Print "-----------------"
Next skpObj
End Sub
In a class, named SpiderKeyPair write this:
Option Explicit
Private m_bIsComplete As Boolean
Private m_sKey As String
Public Property Get IsComplete() As Boolean
IsComplete = m_bIsComplete
End Property
Public Property Get Key() As String
Key = m_sKey
End Property
Public Property Let Key(ByVal sNewValue As String)
m_sKey = sNewValue
End Property
Public Property Let IsComplete(ByVal bNewValue As Boolean)
m_bIsComplete = bNewValue
End Property
When you run the Test Sub in the module you get this:
Falsch
test1
-----------------
-----------------
Wahr
test2
Pay attention to how you initialize new objects. It happens with the word New. Collections are objects and should be initialized as well with New.

Clone a List(Of Class)

I've done some reading and cant seem to wrap my head around what the best approach would be to clone a List(of class) in my VB2010 project. I have three classes that are related like so
Public Class City
'here are many fields of type string and integer
Public Roads As New List(Of Road)
End Class
Public Class Road
'here are many fields of type string and integer
Public Hazards As New List(Of Hazard)
End Class
Public Class Hazard
Implements ICloneable
'here are many fields of type string and integer and double
Public Function Clone() As Object Implements System.ICloneable.Clone
Return Me.MemberwiseClone
End Function
End Class
So lets say I have a city I'm working on, there are cases where I want to create, as a base one road and its hazards, then add another road but using the prior roads hazards as a starting point and then tweak the fields.
Dim rd As New Road
'add road fields
dim hz1 as New Hazard
'add hazard fields
dim hz2 as New Hazard
'add hazard fields
'add the hazard objects to the road
rd.Hazards.Add(hz1)
rd.Hazards.Add(hz2)
'add the road to the city
myCity.Roads.Add(rd)
'here I want to start a new road based on the old road
Dim rdNew As New Road
'copy or clone the hazards from old road
rdNew.Hazards = rd.Hazards '<============
'over-write some of the hazard fields
rdNew.Hazards(0).Description = "temp"
So I know that copying a class will copy the pointer and not the contents. I used the ICloneable interface in the hazard class but cant say I'm using it right. The Hazards variable is a list of Hazard class. How would i go about cloning that class?
Implementing IClonable doesn't mean that it replaces the regular assignment, it will still just copy the reference. And you aren't even copying the items, you are copying the list, which means that you still only have one list, but two references to it.
To use the Clone method you have to call it for each item in the list:
rdNew.Hazards = rd.Hazards.Select(Function(x) x.Clone()).Cast(Of Hazard).ToList()
Imports System.IO
Imports System.Xml.Serialization
Public Function CopyList(Of T)(oldList As List(Of T)) As List(Of T)
'Serialize
Dim xmlString As String = ""
Dim string_writer As New StringWriter
Dim xml_serializer As New XmlSerializer(GetType(List(Of T)))
xml_serializer.Serialize(string_writer, oldList)
xmlString = string_writer.ToString()
'Deserialize
Dim string_reader As New StringReader(xmlString)
Dim newList As List(Of T)
newList = DirectCast(xml_serializer.Deserialize(string_reader), List(Of T))
string_reader.Close()
Return newList
End Function
I know this is old.
rdNew.Hazards = rd.Hazards.ToList()
Even though it's already a list, ToList will create a new list based on it.
With VB 2019, this will create a shallow copy, but that's useful in some circumstances. That means that the list is new, but the elements of both rd.Hazards and rdNew.Hazards point to the same thing.
If you edit any particular hazard, the changes will be seen in both.
If you add a hazard to one list, the other list will not have it.
If you delete a hazard from one list, the other list will still have it.
If Hazard were a primitive type (like a string or integer), then editing items would not be reflected in the other list.

VB.NET ArrayList to List(Of T) typed copy/conversion

I have a 3rd party method that returns an old-style ArrayList, and I want to convert it into a typed ArrayList(Of MyType).
Dim udc As ArrayList = ThirdPartyClass.GetValues()
Dim udcT AS List(Of MyType) = ??
I have made a simple loop, but there must be a better way:
Dim udcT As New List(Of MyType)
While udc.GetEnumerator.MoveNext
Dim e As MyType = DirectCast(udc.GetEnumerator.Current, MyType)
udcT.Add(e)
End While
Dim StronglyTypedList = OriginalArrayList.Cast(Of MyType)().ToList()
' requires `Imports System.Linq`
Duplicate.
Have a look at this SO-Thread: In .Net, how do you convert an ArrayList to a strongly typed generic list without using a foreach?
In VB.Net with Framework < 3.5:
Dim arrayOfMyType() As MyType = DirectCast(al.ToArray(GetType(MyType)), MyType())
Dim strongTypeList As New List(Of MyType)(arrayOfMyType)
What about this?
Public Class Utility
Public Shared Function ToTypedList(Of C As {ICollection(Of T), New}, T)(ByVal list As ArrayList) As C
Dim typedList As New C
For Each element As T In list
typedList.Add(element)
Next
Return typedList
End Function
End Class
If would work for any Collection object.
I would like to point out something about both the DirectCast and System.Linq.Cast (which are the same thing in the latest .NET at least.) These may not work if the object type in the array is defined by the user class, and is not easily convertable into object types that .NET recognizes. I do not know why this is the case, but it seems to be the problem in the software for which I am developing, and so for these we have been forced to use the inelegant loop solution.

Can I use generics to populate List(of t) with custom classes?

I have several different lists I want to call. They all have the same format for the class:
id, value, description, order. Instead of creating a bunch a classes to return the all of the many lists, I wanted to use generics and just TELL it what kind of list to return. However, I can not figure out how to populate the classes.
Here are 2 examples of function in my calling code. This should indicate the type of list and the stored proc used to get the data:
Public Function getTheEyeColors()
Dim glEyeColors As New GenericList
Return glEyeColors.GetALList(Of EyeColor)("GetAllEyeColors")
End Function
Public Function getTheHairColors()
Dim glHairColors As New GenericList
glHairColors.GetALList(Of HairColor)("GetAllHairColors")
End Function
And here is the code I am trying to use to build the generic list...
Public Function GetALList(Of t)(ByVal storedproc As String) As List(Of t)
Dim lstGenericList As New List(Of t)
Dim oGenericListItem As t
Dim oProviderFactory As New ProviderFactory
Dim oConnection As DbConnection
Dim oReader As System.Data.IDataReader
Dim oFactory As DbProviderFactory
Dim oFileMgt As New FileMgt
Dim oCmd As DbCommand
oFactory = oProviderFactory.GetFactory
oConnection = oProviderFactory.GetProviderConnection(oFactory)
oCmd = oConnection.CreateCommand
oCmd.CommandType = CommandType.StoredProcedure
oCmd.CommandText = storedproc
Using (oConnection)
oConnection.Open()
oReader = oCmd.ExecuteReader()
While oReader.Read
HERE IS WHERE I AM NOT SURE HOW TO POPULATE THE EYECOLOR OR HAIRCOLOR CLASS
lstGenericList.Add(oGenericListItem)
End While
oConnection.Close()
End Using
Return lstGenericList
End Function
You could add two generic constraints; I don't know how to express them in VB, but here's the C# version:
T : new() - there has to be a parameterless constructor
T : ICommonInterface - T has to implement an interface
You then put the common properties (ID, Value, Description, Order) into the interface, and you'll be able to create a new T(), set the properties and add it to the list.
EDIT:
The VB Syntax to specify that it must both be creatable and implement an interface is:
(Of T As {ICommonInterface, New})
The way Jon recommends to do it is probably a better way to go, but another way I've seen this done is the FillObject method in the DotNetNuke architecture. Basically it's a conventions based method that uses reflection to match the properties on an object to the values of a dataset.
I personally don't like this method, but it does mean you don't have to create a new implementation of the code to hydrate the object for each stored procedure.
The code is available in the full source download in the DNN project.
While oReader.Read
HERE IS WHERE I AM NOT SURE HOW TO POPULATE THE EYECOLOR OR HAIRCOLOR CLASS
Check out LinqToSql (System.Data.Linq) . You might be re-inventing it.