Delegates and ParamArray - Workaround Suggestions? - vb.net

Some predefined methods contain a ParamArray in their signature.
Delegates, however, cannot contain a ParamArray in their signature.
Question: Assume you wish to create a delegation mechanism for a specific method which requires a ParamArray. How would you work around this constraint?
EDIT: just to make clear, assume you cannot change the method signatures themselves (pre-defined methods, defined by some 3rd party, be it Microsoft or not).
EDIT2: The real deal here is keeping the syntax sugar, because the following code does work, but eliminates the sugar:
Public Delegate Sub MyDelegate(ByVal myArgs() As Object)
Public Sub PredefinedSub(ByVal ParamArray myArgs() As Object)
'...'
End Sub
Sub Test()
Dim aDelegate As New MyDelegate(AddressOf PredefinedSub)
aDelegate.Invoke(New Object() {1, 2, 3, 4})
End Sub
EDIT3: It turns out that Skeet's solutions is applicable also for creating Events and Operators containing a ParamArray.

Hmm... it works in C#:
using System;
class Test
{
delegate void Foo(params string[] args);
static void Main()
{
Foo f = x => Console.WriteLine(x.Length);
f("a", "b", "c");
}
}
However, you're right - the equivalent delegate declaration in VB fails:
Delegate Sub Foo(ParamArray ByVal args() As String)
Gives:
error BC33009: 'Delegate' parameters cannot be declared 'ParamArray'.
Curious. Fortunately, there's a way round it:
Imports System
Public Class Test
Delegate Sub Foo(<[ParamArray]()> ByVal args() As String)
Public Shared Sub Main()
Dim f As Foo = AddressOf PrintLength
f("a", "b", "c")
End Sub
Private Shared Sub PrintLength(ByVal x() As String)
Console.WriteLine(x.Length)
End Sub
End Class
Basically I've just applied ParamArrayAttribute manually. Seems to work fine.
However, none of this would have stopped you from using existing ParamArray methods anyway. Those methods are quite capable of taking normal arrays - so you could have declared your delegate types as normal and still created delegate instances which referred to those methods with no problems at all. The delegate type only affects how you would be able to call the delegate.
Other than declaring a delegate type with a parameter array, I don't really see what the issue was.

Are you sure that delegates do not support ParamArray? Ok, even if they don't, ParamArray is syntax sugar for plain old array. define parameter as array, that's it.

Related

Implementation inheritance in VBA?

I have an interface IValidator, and I want classes that implement IValidator to have access to a shared set of properties and methods, but it seems VBA doesn't have true implementation inheritance, so instead I have a class BaseValidator with the shared resources, which is used as a delegate in the subclasses.
Interface:
'IValidator
Public Function isValid(columnName As String) As Boolean
End Function
'used for initializing delegate
Public Sub setup(fieldsDict As Dictionary)
End Sub
Base class:
'BaseValidator
Public fieldsDict As Dictionary
Public Sub setup(fieldsDict As Dictionary)
Set Me.fieldsDict = fieldsDict
End Sub
Public Function doSomethingWithFieldsDict(columnName as string) as variant
'do something
End Function
Example implementation:
'Validator_FeatureNumber
Private bv As New BaseValidator
Implements IValidator
Public Sub IValidator_setup(fieldsDict As Dictionary)
bv.setup fieldsDict
End Sub
Function IValidator_isValid(columnName As String) As Boolean
IValidator_isValid = IsNumeric(bv.doSomethingWithFieldsDict(columnName))
End Function
This works, but it means I have to duplicate the IValidator_setup() code block in every implementation, which seems like a bad idea for code maintainability. Is there any way to have a subclass inherit methods from a superclass in VBA?
The short answer is no.
Welcome to the the fun and exiting world of 1991:) VBA is a subset of VB6. Inheritance was not supported at that time, and because Microsoft based VBA on VB6 and then abandoned* it when they went to .Net, that means it likely never will be:(
*They did update it somewhat to cope w/64 bit API calls, but that was pretty much it.

Passing Method as Delegate Parameter Issues

I'm used to programming in C# so I have no idea how to approach delegates and passing methods in VB
The error that I am getting is: Argument not specified for parameter 'message' of 'Public Sub ReceiveMessage(message As String)'
Here is the constructor of the class that I am trying to pass to:
Delegate Sub ReceiveDelegate(message As String)
Public ReceiveMethod As ReceiveDelegate
Sub New(ByRef receive As ReceiveDelegate)
ReceiveMethod = receive
End Sub
This is the method that I am trying to pass to that constructor:
Public Sub ReceiveMessage(message As String)
MessageBox.Show(message)
End Sub
I'm using it as such:
Dim newClass As New Class(ReceiveMessage)
The purpose of this, is that once the class receives data from a network device, it can call the corresponding method on the Form asynchronously.
You need to create the delegate object and use the AddressOf operator, like this:
Dim newClass As New Class(New ReceiveDelegate(ReceiveMessage))
However, if you don't explicitly create the delegate object, VB.NET will automatically determine the right type, based on the signature, and create it for you, so you can just do it like this:
Dim newClass As New Class(AddressOf ReceiveMessage)
The latter is obviously less typing, but the former is more explicit. So, take your pick. Both ways are perfectly acceptable and common.

What are delegates, how are they used?

After being alerted by a user that the basis of my question was based on erroneous knowledge I have edited the title of this question with what should have been my original question and have erased the previous content. Below is, what I think, an excellent explanation of delegates.
From what you are saying, your ideas regarding delegates do not seem to be completely clear. Thus, the whole point of this answer is clarifying what delegates actually are such that you can apply this knowledge to understand the code you propose or any other delegate-related situation.
Delegates are a way to treat functions as variables. That is, instead of doing Dim myString as String = "this", substituting "this" with a function.
Simple code to clarify what a delegate is and how it has to be treated:
Public Class Form1
Public Delegate Sub subDelegate(arg1 As String, arg2 As String)
Public subDelegateVar As subDelegate
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
subDelegateVar = New subDelegate(AddressOf origSub)
subDelegateVar.Invoke("this", "that")
End Sub
Public Sub origSub(arg1 As String, arg2 As String)
MsgBox("I want to write " & arg1 & " and " & arg2)
End Sub
End Class
You have the function (Sub) origSub and you want to treat it as a variable. First thing you have to do is declaring a delegate matching its structure:
Public Delegate Sub subDelegate(arg1 As String, arg2 As String)
This is like defining a type (the string type in the example above). Next step is declaring a variable associated with this type (myString in the example above), what is done with the following code:
Public subDelegateVar As subDelegate
And the third step is assigning this variable to the value you want (myString = "this") what is done via:
subDelegateVar = New subDelegate(AddressOf origSub)
What is Invoke here for? Just for calling the given function. Why creating a new variable (delegate), assigning a function to it and using Invoke to call the function instead of calling this function directly? Because some times you need the function to be treated as a variable; for example: when you want to pass it (the whole function) as an argument to another function -> this is one of the reasons why delegates are required, not the only one (not even close).

Interface does not behave like an Object?

I have a little problem with an interface. A bunch of my classes implement the ILayoutObject interface. A method declares a variable as ILayoutObject (defaulting it as Nothing) and then runs some code which decides which object it should be. The problem is, the evaluation code runs in a method which receives the variable as a parameter and assigns an object to it. With objects, this would be no problem. The object would be affected by the changes in the method and everything would be OK. Howeverm, when using an interface, the variable in the calling code remains Nothing and behaves like a normal variable. Does anyone have any ideas on how to circumvent that? Alas, due to code structure I am unable to use ByRef or functions :(
Here is some code:
Protected LayoutHandler As Dictionary(Of String, Action(Of Constants.OptionsEntryStructure, ILayoutElements)) = New Dictionary(Of String, Action(Of Constants.OptionsEntryStructure, ILayoutElements)) From
{
{Constants.KeyLayoutType, AddressOf KeyLayoutType}
}
Sub MakeLayOuts
Dim LayoutElement As ILayoutElements = Nothing
Dim Value = "SomeValues"
Dim key = "Key"
LayoutHandler(key)(Value, LayoutElement)
' LayoutElement remains nothing.....
End Sub
Protected Sub KeyLayoutType(elem As Constants.OptionsEntryStructure, Layout As ILayoutElements)
Layout = New LayoutObject 'which would implement the interface
End Sub
You need to declare the parameter as ByRef if you want to alter the object to which the variable in the calling code points to:
Protected Sub KeyLayoutType(elem As Constants.OptionsEntryStructure, ByRef Layout As ILayoutElements)
Layout = New LayoutObject 'which would implement the interface
End Sub
This is true with any reference type (classes). The fact that they are referenced with an interface makes no difference.
If you can't use ByRef, and you can't use a function to return the new object, then your only other real option would be to request a type of object which has the layout object as a property. For instance:
Public Interface ILayoutElementContainer
Public Property LayoutElement As ILayoutElements
End Interface
Protected Sub KeyLayoutType(elem As Constants.OptionsEntryStructure, Container As ILayoutElementContainer)
Container.LayoutElement = New LayoutObject 'which would implement the interface
End Sub

Constructor within a constructor

Is this a bad idea? Does calling a generic private constructor within a public constructor create multiple instances, or is this a valid way of initializing class variables?
Private Class MyClass
Dim _msg As String
Sub New(ByVal name As String)
Me.New()
'Do stuff
End Sub
Sub New(ByVal name As String, ByVal age As Integer)
Me.New()
'Do stuff
End Sub
Private Sub New() 'Initializer constructor
Me._msg = "Hello StackOverflow"
'Initialize other variables
End Sub
End Class
That's perfectly valid and a commonly used way to reuse constructor code. Only one object is instantiated.
It is a valid approach. There are some caveats with where the new function can be called:
The Sub New constructor can run only once when a class is created. It
cannot be called explicitly anywhere other than in the first line of
code of another constructor from either the same class or from a
derived class.
Read more about the object lifetime on MSDN.
Chaining constructors like this will certainly not create additional object instances.
It is desirable to only write code for a certain portion of initialization once. This is a common and valid initialization pattern.