What are delegates, how are they used? - vb.net

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).

Related

How can I assign a value to a number of different variables in a collection using loops?

I have a problem that has been bugging me for a while now. Consider this code:
Public Class Class1
Dim VariableList as New List(of Object) From {MainForm.X, MainForm.Y,
SettingsForm.Z, SettingsForm.Textbox1.Text} '...etc.
Sub SetToZero()
For Each Element in VariableList
Element = 0
Next
End Sub
Sub SetToCustomValue(value As Double)
For Each Element in VariableList
Element = value
Next
End Sub
Sub LoadValuesFromFile()
Dim path As String = MainForm.GetPath()
For Each Element in VariableList
Element = File.Readline()
Next
End Sub
Sub SaveValuesToFile()
Dim path As String = MainForm.GetPath()
For Each Element in VariableList
Element = File.Writeline()
Next
End Sub
'and more similar functions/subs
As you can see, what this class does is that it takes lot of different variables from different places into a collection, and then various functions read or write values to every variable in that collection using loops. In this example, I have just a few variables, but most of the time there are dozens.
Reading the values is not a problem. Writing them, is, because when I declare that VariableList at the top of my class, that List just makes a copy of each variable, rather than maintaining a reference to it. Meaning that if, say, one of the functions modifies the MainForm.X in that List, the actual variable MainForm.X is not modified. To work with references, I would have to forgo loops, and assign every single variable manually, in every function. Which is obviously a lot of bad code. I want to declare that list of variables only once, and then use loops, like in this example code that I wrote above. My question is, how can I make such a container (List, Array, whatever) that would retain the references to the original variables in it, and make the code above possible?
There is no easy way to store pointers to variables in VB.NET. As a workaround, you can use a class to store your variables, as a class is always used as a pointer.
Here's an example of a way to achieve this with a ContainerClass which own a Dictionary of integers. One interest of this method would be that you can declare and name "variables" dynamically. In reality, they will be managed KeyValuePair. Once you have instantiated a copy of this class, you can use it to "manage" your variables by using this class as your pointer.
I included a loop which set all the integers to the same number just for fun, and to demonstrate the kind of manipulation which would end up having an effect similar to one of those described in your question.
Public Class Form2
'This is the container class which will be used to bypass the lack of pointers
'if you wanted to change a property, like the window width, it would be more difficult, but simples variables will be no trouble
Private variableContainer As New VariableContainer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
variableContainer.AddVar("X", 5)
variableContainer.AddVar("Y", 15)
Debug.Print(variableContainer.GetVar("X"))
Debug.Print(variableContainer.GetVar("Y"))
variableContainer.SetAllVar(42)
Debug.Print("Next line will print 42")
Debug.Print(variableContainer.GetVar("X"))
End Sub
End Class
Public Class VariableContainer
'I know a public variable wouldn't need the fancy functions down there, but it's usually better to encapsulate, especially if you're working with a team
'and "future you" count as a teammate, never forget that...
Private list As New Dictionary(Of String, Integer)
Public Sub AddVar(ByVal name As String, ByVal value As Integer)
list.Add(name, value)
End Sub
Public Function GetVar(ByVal name As String) As Integer
If list.ContainsKey(name) Then
Return list(name)
Else
'I choose -1 arbitrarily, don't put too much thinking into this detail
Return -1
End If
End Function
Public Sub SetVar(ByVal name As String, ByVal num As Integer)
If list.ContainsKey(name) Then
list(name) = num
End If
End Sub
Public Sub SetAllVar(ByVal num As Integer)
Dim dict As New Dictionary(Of String, Integer)
For Each item As KeyValuePair(Of String, Integer) In list
dict.Add(item.Key, num)
Next
list = dict
End Sub
End Class
Have fun!

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.

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

Why do Property Setters get called more often than expected?

I have observed a behaviour in VB.net where property setters get called more often than seems necessary, in conjunction with calls to the sister setter method.
Public Class Form1
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Console.WriteLine("Calling WorkReferenceTypeByReference")
WorkReferenceTypeByReference(ReferenceTypeData)
Console.WriteLine("Called WorkReferenceTypeByReference")
Console.WriteLine("Calling WorkReferenceTypeByValue")
WorkReferenceTypeByValue(ReferenceTypeData)
Console.WriteLine("Called WorkReferenceTypeByValue")
End Sub
Public Sub WorkReferenceTypeByReference(ByRef ref As Point)
Dim b As Point = New Point(4, 4) + ref
Console.WriteLine(" adding (4,4) to " & ref.ToString)
End Sub
Public Sub WorkReferenceTypeByValue(ByVal ref As Point)
Dim b As Point = New Point(4, 4) + ref
Console.WriteLine(" adding (4,4) to " & ref.ToString)
End Sub
Private m_ReferenceType As Point = New Point(0, 0)
Public Property ReferenceTypeData As Point
Get
Console.WriteLine(" Calling ReferenceTypeData getter")
Console.WriteLine(" returning: " & m_ReferenceType.ToString)
Return m_ReferenceType
End Get
Set(ByVal value As Point)
Console.WriteLine(" Calling ReferenceTypeData setter")
Console.WriteLine(" value = " & value.ToString)
m_ReferenceType = value
End Set
End Property
End Class
The previous code returns to the console the following output
Calling WorkReferenceTypeByReference
Calling ReferenceTypeData getter
returning: {X=0,Y=0}
adding (4,4) to {X=0,Y=0}
Calling ReferenceTypeData setter
value = {X=0,Y=0}
Called WorkReferenceTypeByReference
Calling WorkReferenceTypeByValue
Calling ReferenceTypeData getter
returning: {X=0,Y=0}
adding (4,4) to {X=0,Y=0}
Called WorkReferenceTypeByValue
Note the spurious call to the property setter following the method execution. I am supposing this behaviour is produced as a safety measure against inadvertently modifying the underlying property, despite this potentially being the intent.
This behaviour in the case of ByRef vs ByVal usage is easily solved by choosing appropriate ByVal keyword, however hase recently noticed a more insidious behaviour, one that has caused a stack overflow of repeated calls, since the setter call would update a value that called the getter only.
Public Sub DoSomething()
Dim a As New CustomObject(anotherObject.AProperty(getterArgument))
End Sub
Public Class AnotherObject
Public Property AProperty as SomeType
Get
' Get value
End Get
Set
' Set value, call DoSomething
End Set
End Property
End Class
In the previous example, calling DoSomething() would fire the AProperty getter method, but then after that usage, would fire the setter method, which by program logic calls DoSomething() again. It is the automatic calling of the setter that puzzles me.
This is, in fact, a feature of VB.Net. In your code, you are passing a property, not a variable, by reference. Strictly speaking, passing a property ByRef is not possible, because ByRef needs a reference to a variable. However, the compiler automatically creates a temporary local on your behalf and passes it to the method for you. Because the method may change the ByRef parameter, which is now the compiler-generated temporary and not your property, the compiler then inserts a call to the setter. Essentially, something like this happens:
Dim temp = Me.ReferenceTypeData
Me.WorkReferenceTypeByReference(temp)
Me.ReferenceTypeData = temp
Other languages, such as C#, do not allow passing a property by reference (rightly so from the strict definition of parameter passing) and instead require you to write the equivalent of the above code yourself.
It is a VB.net "feature". You won't see this in C#. VB.NET will copy an object (in this case, a pointer) twice when you use ByRef.
See http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/bc54294f-785a-467b-96ec-12d0387074e9/
"As far as I understand VB.NET, ByRef copies a parameter value twice: once when entering the method and once when returning from the method. "
So, at the end of method execution, it is basically copying itself, causing the setter to be called.
That said, there is really no point in using ByRef with any object, it's just passing the pointer anyway when you use ByVal so the effect of being able to modify the object is the same. ByRef is only useful for value types.

Delegates and ParamArray - Workaround Suggestions?

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.