Why do Property Setters get called more often than expected? - vb.net

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.

Related

How to declare public variable while it's requires if function to avoid null refrence in vb [duplicate]

I am trying to pass an equipment object to a form object and then use that equipment object in a click event from a button on the form. But I don't know how to properly reference the equipment object within the button event.
I set up the new form instance using:
Public Sub New(ByRef thisEquip As classEquipment)
Me.InitializeComponent()
Me.Text = thisEquip.equipName & " Tests"
End Sub
and set up the button click event like this:
Private Sub btnUpdateAndClose_Click(sender As Object, e As EventArgs) Handles btnUpdateAndClose.Click
Call updateTestList(thisEquip)
End Sub
But the 'thisEquip' object is not recognized. I think this is because the sender is the button and not the form itself. However, I don't know how to reference the equipment object from the form.
The Scope depends on where a variable is declared. You might have missed something skimming the link - each scope level summary includes the phrase in which it is declared.
Now look at your constructor:
Public Sub New(ByRef thisEquip As classEquipment)
thisEquip is declared as an argument to the constructor. Thus, it only exists in that procedure. The fact that the procedure is in a form or that thisEquip is mentioned in the form (or module or anything else) is incidental. While it is true that the constructor is special in several ways, in matters of Scope, it is just another procedure.
Form Level Scope
To save a reference to it for use elsewhere:
Public Class Form1
' declare a variable to hold the reference
Private myEquip As classEquipment
' declare an array
Private myImgs As Image()
Public Sub New(ByRef thisEquip As classEquipment)
InitializeComponent()
...
myEquip = thisEquip ' assign param to the var
' assign array of images to the Form level var
' via a temp array
myImgs = New Image() {My.Resources.add,
My.Resources.ballblack, My.Resources.ballblue,
My.Resources.ballgreen}
End Sub
Declared at the form level, it has form/class level scope. You can now reference myEquip or myImgs anywhere in the form. Do Not Use Dim when merely assigning something to a form level object - it will create a new local, but identically named variable.
Other common scope levels:
Procedure Level Scope
Private myFoo as Int32
Private Sub DoSomething()
Dim myBar As String
myBar = "Ziggy"
...
Dim myFoo As Int32 = 7
End Sub
This is more often called local scope. I am using procedure level because it compares and contrasts better to the other terms.
myBar is declared in the DoSomething method, so it has procedure level scope - it only exists in that method. Trying to use it elsewhere will result in an error. This is similar to the constructor example above with the main difference being that the thisEquip object was passed as a parameter rather than declared locally.
This leads some to get confused: the Dim myFoo in the method declares (creates!) a new, local-only myFoo variable which has no relation to the Form/Class level variable of the same name. The local version shadows out the other. Part of the confusion for this seems to be that some think they need to (re) use Dim before they can use a variable. You do not.
Block Level Scope
Directly from MSDN:
If n < 1291 Then
Dim cube As Integer
cube = n ^ 3
End If
A fair number of VB statements create a block scope (For Each/Next, If/End If and Using/End Using). Variables declared inside a Block, have a scope limited to that block. Basically, (almost) anything which results in indentation creates a Block Scope.
Private Sub .....
Dim cube As Int32
If n < 1291 Then
cube = n ^ 3
End If
Now, cube can be used elsewhere in the procedure: its scope has been changed from Block to Local.
For more details, see MSDN:
- Scope In Visual Basic
- Value Types vs Reference Types

WriteOnly Property ByRef

I don't understand how I can pass an argument byref in VB.NET.
I tried this:
Private m_Form As frmMain
Public WriteOnly Property MyForm() As Form
Set(ByRef value As Form)
m_Form = value
End Set
End Property
But VB.NET does not like the "Byref" argument.
Can somebody help?
Thank you!
The ByRef modifier cannot be used in property setters.
It can only be declared in method'ss and constructor's signatures. There it specifies that the underlying variable of an argument can be modified in the called method.
In the following example the ByRef modifier causes the field named "underlyingVariable" to take the new value. By passing the variable by Value, it would not get modified and therefore would be null:
Private underlyingVariable As Object
Public Sub New()
MyMethod(underlyingVariable)
End Sub
Public Sub MyMethod(ByRef o As Object)
o = New Object()
End Sub
You cannot pass things by reference with setters. It must be ByVal. From the VB.NET Specification:
ยง9.7.2 If a parameter list is specified, it must have one member, that member must have no modifiers except ByVal, and its type must be the same as the type of the property.
I don't think it particularly makes sense to use ByRef in a property setter. Using ByRef implies that you may want to change the reference of what invokes the setter.
Form is a reference type (class), so you want to pass it by value. Otherwise you are passing a reference of a reference type.

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