Passing List (Of ChildClass) as parameter to method expecting List (Of ParentClass)? - vb.net

I have implemented inheritance for two parent classes called Table and Field. Each parent class has several child classes. In the Table parent class, I created a method that expects parameter List(Of Field).
I get an error when trying to pass in parameter List(Of ChildField) to the method that expects a parameter of List(Of Field). The error message is as below:
Value of type
'System.Collections.Generic.List(Of
com.hlb.icisbatch.data.ChildField)'
cannot be converted to
'System.Collections.Generic.List(Of
com.hlb.icisbatch.data.Field)'
My question, is it possible to pass in list of child class as parameter? If it is not a list then it works fine. But somehow it is not possible with lists?
Below is sample class structure:
Table Field
| |
ChildTable ChildField
I have a method in the parent class:
Public Class Table
Public Sub New()
End Sub
Public Overridable Sub setFields(ByVal list As List(Of Field)
'Do work here'
End Sub
End Class
and method in child class:
Public Class ChildTable
Public Sub New(ByVal list As List(Of ChildField)
setFields(ChildField)
End Sub
End Class

No, this isn't possible - and for good reason. Consider what would happen if the calling code tried to add an instance of Field (or some other derived class) to the list. Bad stuff would happen.
In .NET 4 (and presumably VB10) this is possible via covariance and contravariance of generic delegates and interfaces (not classes) - but only where it's safe. So IList(Of T) is not variant (because values go "in" and "out" of the API); IEnumerable(Of T) is covariant (so there's a conversion from IEnumerable(Of String) to IEnumerable(Of Object)) and IComparer(Of T) is contravariant (so there's a conversion from IComparer(Of Object) to IComparer(Of String) for example).
(This ability has actually been in the CLR since v2.0, but it's only surfaced in the languages and framework for .NET 4.)

So, for .Net 4.0+ (possibly earlier versions too) you'd have to alter your setFields method:
Public Overridable Sub setFields(As T Of Field)(ByVal list As List(Of T)
'Do work here'
End Sub
Then when you call it with:
Public Sub New(ByVal list As List(Of ChildField)
setFields(list)
End Sub
it is essentially calling:
setFields(Of ChildField)(list)
which means that the setFields knows it is dealing with a list of ChildField objects and won't let anyone do anything naughty like add a different ChildNumber2Field object.
Although generics were introduced in .Net 2.0, I'm not sure exactly how advanced that version was wrt doing this kind of thing.

Related

How to extend the List class Of any type

I am trying to learn class extensions and am running into difficulty when defining the type of variables accepted in my new methods & functions. I have this simple method that I want to add to the List object that would be a short-hand for removing the final element off the list.
Imports System.Runtime.CompilerServices
Module ListExtensions
<Extension()>
Sub RemoveLast(FromList As List(Of ))
FromList.RemoveAt(FromList.Count - 1)
End Sub
End Module
I don't know what to write inside the brackets for List(Of ). For numeric operations, I have heard that I am supposed to create duplicate versions of the method with each accepted numeric type. But I want to accept lists of any type, and don't want to create a hundred of this method.
What do I write?
(Promoting GSerg's comment to an answer...)
You can write generic methods as extension methods; in fact, Linq is all generic extension methods. The result would be something like this:
<Extension>
Public Sub RemoveLast(Of T)(ByVal this As List(Of T))
this.RemoveAt(this.Count - 1)
End Sub
The generic argument will be inferred in the call, you don't need to specify it, e.g. this will work:
Dim myList As New List(Of Integer) From {1, 2, 3, 4}
myList.RemoveLast()

Is there a way to determine the value of a property on a form that calls a method in a seperate class library

Specifically aimed at winforms development.
I suspect that the answer to this is probably No but S.O. has a nice way of introducing me to things I didn't know so I thought that I would ask anyway.
I have a class library with a number of defined methods therein. I know from personal experimentation that it is possible to get information about the application within which the class library is referenced. What I would like to know is whether it would be possible to get information about the value of a property of a control on a form when a routine on that form calls a method in my class library without passing a specific reference to that form as a parameter of the method in the class library?
So purely as an example (because it's the only thing I can think of off the top of my head). Is there a way that a message box (if it had been so designed to do so in the first place) could 'know' from which form a call to it had been made without that form being specifically referenced as a parameter of the message box in the first place?
Thanks for any insights you might have.
To address the example of the MessageBox, in many of the cases you can use the active form. You can retrieve it by using Form.ActiveForm. Of course, as regards the properties that you can request, you are limited to the properties provided by the Form or an interface that the Form implements and that the method in the other assembly also knows. To access other properties you can use Reflection, but this approach would neither be straightforward nor would it be clean.
In a more general scenario, you could provide the property value to the method as a parameter. If it is to complex to retrieve the value of the property and the value is not needed every time, you can provide a Func(Of TRESULT) to the method that retrieves the value like this (sample for an integer property):
Public Sub DoSomethingWithAPropertyValue(propValFunc As Func(Of Integer))
' Do something before
If propertyValueIsNeeded Then
Dim propVal = propValFunc()
End If
' Do something afterwards
End Sub
You call the method like this:
Public Sub SubInForm()
Dim x As New ClassInOtherAssembly()
x.DoSomethingWithAPropertyValue(Function() Me.IntegerProperty)
End Sub
I kind of question your intentions. There's no problem sending the information to a function or the constructor.
Instead of giving the information to the class, the class would ask for the information instead using an event.
Module Module1
Sub Main()
Dim t As New Test
AddHandler t.GetValue, AddressOf GetValue
t.ShowValue()
Console.ReadLine()
End Sub
Public Sub GetValue(ByRef retVal As Integer)
retVal = 123
End Sub
End Module
Class Test
Public Delegate Sub DelegateGetValue(ByRef retVal As Integer)
Public Event GetValue As DelegateGetValue
Public Sub ShowValue()
Dim val As Integer
RaiseEvent GetValue(val)
Console.WriteLine(val)
End Sub
End Class

Expose .NET DataTable properties to VBA via COM Interface

I am trying to create a .Net DLL basically as an abstraction layer for database connections; it is going to replace a current DLL we have that is written in VB6 and I am trying to match the current functionality as much as possible.
Anyway, the essential issue I am having is that I can't find a way to get .Net classes like DataColumnCollection or DataColumn to display in the VBA Interpreter -- It may say, for example, "Column" with the type "MarshalByValueComponent," but the value will be "No Variables".
I can get it to work if I completely re-create both classes (i.e. Fields as a collection of field, which inherits from DataColumn, and then define an interface for both), but that seems like a lot of added overhead for what (should be?) a pretty simple idea. I feel like I am just missing something very simple with the way the marshaller is handling the DataColumn class.
A lot of the stuff I am finding online is on how to convert a DataTable or DataReader to a legacy ADODB Recordset, but that also would add a lot of overhead... I'd rather leave it as a DataTable and create a COM interface to allow VBA to interact with it; that way if, for example, they want to write the table to an excel sheet, I wouldn't be duplicating work (convert to ADODB recordset, then read/write to excel sheet. You'd need to iterate the entire table twice...)
Sorry for the book-length explanation -- I felt the problem needed a bit of clarification since the root-cause is trying to match legacy functionality. Here is an example of my current interface that does not work:
Public Interface IDataTable
ReadOnly Property Column As DataColumn
End Interface
<ClassInterface(ClassInterfaceType.None)> _
<System.ComponentModel.DesignerCategory("")> _
<ComDefaultInterface(GetType(Recordset.IDataTable))> _
<Guid("E7AFBBB6-CB20-44EC-9CD2-BC70B94CD8B7")> _
Public Class Recordset : Inherits Data.DataTable : Implements IDataTable
Public ReadOnly Property Column As DataColumn Implements IDataTable.Column
Get
Return MyBase.Columns(0)
End Get
End Property
Note: I originally tried the property Columns as DataColumnCollection which returned MyBase.Columns. That came through as an Object, instead of MarshalByValueComponent, but was also empty. I know MyBase.Column(0) has a value, because I can put Msgbox(MyBase.Columns(0).ColumnName) right above the return in the get and it pops up fine (don't judge; this is way easier than using a debugger for this)...
I wouldn't mind just defining them both, but I can't inherit DataColumnCollection and the COM interface already sucks at dealing with generics. Is there any other way around this without re-inventing the wheel?
Thanks for your help!
I just spent the last 3 weeks doing something eerily similar.
I ended up making two .NET assemblies:
A pure .NET assembly that talks to the datastore (for use by .NET apps).
A "COM Interop" assembly that wraps the first assembly and adds the COM overhead (ADODB references and COM-Visible interfaces).
I call the second assembly from Excel VBA using the VSTO "AddIn.Object" property.
I ended up converting System.Data.DataTables to ADODB.Recordsets as you mentioned. Getting .NET and VBA talking about anything other than primitive types was beyond-frustrating for me. In fact, I ended up serializing some objects as JSON so the two worlds could communicate.
It does seem insane, but I reinvented the wheel.
I followed this MSDN article to make my .NET code callable by VBA.
I used this Code Project article (I'm sure you've seen) to convert to Recordset*.
I let the frameworks handle string, integers, etc.
For all other data types I used Json.Net and a custom VBA class to do JSON serialization.
*Converted article to VB.Net and added some extra error handling.
Okay, this probably isn't the most elegant (or complete, at this point) solution; but I think it's the route I am going to go.
Instead of converting the whole thing to an ADODB Recordset (and duplicating any iterations), I just threw out the DataTable class entirely and wrote my own Recordset class as a COM Wrapper for the a generic Data Reader (via the IDataReader interface) and added a new Field class to manage the type conversion and set up Fields as an array of Field (since interop hates generics)
It basically creates a forward-only ADODB Recordset (same limitations) but has the benefit of only loading one row at a time, so the bulk of the data can be handled as managed code until you know what they want to do with it (I'm going to add methods for ToArray, ToAccessDB, ToFile, etc that use the reader) while still allowing the ability to iterate through the Recordset from excel/access/vbscript/vb6 (if that's really what they want to do.. mostly needed that for legacy support anyway)
Here is an example, in case anyone else has to do this again; somewhat modified for brevity:
Public Interface IRecordset
ReadOnly Property CursorPosition As Integer
ReadOnly Property FieldCount As Integer
ReadOnly Property Fields As Field()
Function ReadNext() As Boolean
Sub Close()
End Interface
<System.ComponentModel.DesignerCategory("")> _
<ClassInterface(ClassInterfaceType.None)> _
<ComDefaultInterface(GetType(IRecordset))> _
<Guid("E7AFBBB6-CB20-44EC-9CD2-BC70B94CD8B7")> _
Public Class Recordset : Implements IRecordset : Implements IDisposable
Private _Reader = Nothing
Private _FieldCount As Integer = Nothing
Private _Fields() As Field
Public ReadOnly Property CursorPosition As Integer Implements IRecordset.CursorPosition...
Public ReadOnly Property FieldCount As Integer Implements IRecordset.FieldCount...
Public ReadOnly Property Fields As Field() Implements IRecordset.Fields...
Friend Sub Load(ByVal reader As IDataReader)
_Reader = reader
_FieldCount = _Reader.FieldCount
_Fields = Array.CreateInstance(GetType(DataColumn), _FieldCount)
For i = 0 To _FieldCount - 1
_Fields(i) = New Field(i, Me)
Next
End Sub
'This logic kinda sucks and is dumb.
Public Function ReadNext() As Boolean Implements IRecordset.ReadNext
_EOF = Not _Reader.Read()
If _EOF Then Return False
_CursorPosition += 1
For i = 0 To _FieldCount - 1
_Fields(i)._Value = _Reader.GetValue(i).ToString
Next
Return True
End Function
From here you just need to define some type like Field or Column and add an interop wrapper for that type:
Public Interface IField
ReadOnly Property Name As String
ReadOnly Property Type As String
ReadOnly Property Value As Object
End Interface
<System.ComponentModel.DesignerCategory("")> _
<ClassInterface(ClassInterfaceType.None)> _
<Guid("6230C670-ED0A-48D2-9429-84820DC2BE6C")> _
<ComDefaultInterface(GetType(IField))> _
Public Class Field : Implements IField
Private Reader As IDataReader = Nothing
Private Index As Integer = Nothing
Public ReadOnly Property Name As String Implements IField.Name
Get
Return Reader.GetName(Index)
End Get
End Property
Public ReadOnly Property Value As Object Implements IField.Value
Get
Return Reader.GetValue(Index)
End Get
End Property
Public ReadOnly Property Type As String Implements IField.Type
Get
Return Reader.GetDataTypeName(Index).ToString
End Get
End Property
Sub New(ByVal i As Integer, ByRef r As IDataReader)
Reader = r
Index = i
End Sub
End Class
All of this is rather silly, but it seems to work well.
Note: I've only been using .Net for about 4 days now, so this might be terrible, please feel free to comment on anything extremely stupid I might be doing.

VB.NET Why is this subroutine declared this way?

VB.NET 2010, .NET 4
I have a basic question: I have a subroutine that I found somewhere online declared thusly:
Public Sub MyFunction(Of T As Control)(ByVal Control As T, ByVal Action As Action(Of T)) ...
I'm wondering about the (Of T As Control) part of the declaration after the sub's name. I see that T is used later in specifying the type of Control and in Action(Of T), but why is it done this way instead of just doing:
Public Sub MyFunction(ByVal Control As Control, ByVal Action As Action(Of Control)) ...
What does that part after the sub's name mean? What is its purpose? Thanks a lot and sorry for my ignorance.
That is VB.NET's generic method declaration syntax:
A generic type is a single programming
element that adapts to perform the
same functionality for a variety of
data types. When you define a generic
class or procedure, you do not have to
define a separate version for each
data type for which you might want to
perform that functionality.
An analogy is a screwdriver set with
removable heads. You inspect the screw
you need to turn and select the
correct head for that screw (slotted,
crossed, starred). Once you insert the
correct head in the screwdriver
handle, you perform the exact same
function with the screwdriver, namely
turning the screw.
(Of T) is a generic type parameter, adding As Control constrains the type of T to inherit from Control. You could write the method the second way, but you'd probably end up having to cast the Control to whatever inherited type, within the lambda expression in the Action, or in the body of MyFunction. Generics allow you to avoid that.
For example:
Sub Main()
Dim form As New Form()
Dim textBox As New TextBox
Dim listBox As New ListBox
MyFunction(textBox, Sub(c) c.Text = "Hello")
MyFunction(listBox, Sub(c) c.Items.Add("Hello"))
MyFunction2(textBox, Sub(c) c.Text = "Hello")
MyFunction2(listBox, Sub(c) CType(c, ListBox).Items.Add("Hello"))
End Sub
Public Sub MyFunction(Of T As Control)(ByVal Control As T, ByVal Action As Action(Of T))
Action(Control)
End Sub
Public Sub MyFunction2(ByVal Control As Control, ByVal Action As Action(Of Control))
Action(Control)
End Sub
It doesn't look too valuable in trivial cases, but it's invaluable for more complex cases.
As others have said, it's a constrained generic parameter. But no one has yet addressed this part of your question:
why is it done this way
The answer is in the action. If it were just declared as a Control, you wouldn't be able to do something like this, because not all controls have a .Text property*:
MyFunction(MyTextBox, Function(t) t.Text = "new value" )
The body of the function just needs to know that it's working on a control of some kind, but the Action(Of T) you pass to the function might want to know the actual type of the control.
Yes, all controls do have a .Text property. Let's pretend for a moment that some didn't

Iterate through generic list of unknown type at runtime in VB.Net

Does anyone know how to iterate over a generic list if the type of that list isn't known until runtime?
For example, assume obj1 is passed into a function as an Object:
Dim t As Type = obj1.GetType
If t.IsGenericType Then
Dim typeParameters() As Type = t.GetGenericArguments()
Dim typeParam As Type = typeParameters(0)
End If
If obj is passed as a List(Of String) then using the above I can determine that a generic list (t) was passed and that it's of type String (typeParam). I know I am making a big assumption that there is only one generic parameter, but that's fine for this simple example.
What I'd like to know is, based on the above, how do I do something like this:
For Each item As typeParam In obj1
'do something with it here
Next
Or even something as simple as getting obj1.Count().
The method that iterates over your list can specify a generic type:
Public Sub Foo(Of T)(list As List(Of T))
For Each obj As T In list
..do something with obj..
Next
End Sub
So then you can call:
Dim list As New List(Of String)
Foo(Of String)(list)
This method makes the code look a little hairy, at least in VB.NET.
The same thing can be accomplished if you have the objects that are in the list implement a specific interface. That way you can populate the list with any object type as long as they implement the interface, the iteration method would only work on the common values between the object types.
If you know that obj is a Generic List. Then you're in luck.
Generic List implements IList and IEnumerable (both are non-generic). So you could cast to either of those interfaces and then For Each over them.
IList has a count property.
IList also has a Cast method. If you don't know the type to cast to, use object. This will give you an IEnumerable(Of object) that you can then start using Linq against.