Structures and Delegates and ByRef Arguments

E

eBob.com

If I have a structure and I pass it ByRef to a Subroutine via a Delegate,
any changes to the structure are not seen by the caller. This sure seems
unfortunate to me and I would welcome any explanation of why this is
reasonable behavior. In any event I think that the Delegate documentation
should point this out in bold face type. The following code illustrates the
problem:

Option Explicit On
Option Strict On

Public Class Form1
Inherits System.Windows.Forms.Form

Delegate Sub delegTestSub(ByRef si As SiteRunOpts)

Structure SiteRunOpts
Public set_by_Load_code As String
Public set_by_TestSub_code As String
End Structure

Public Shared Sites(0) As SiteRunOpts

Public onesite As SiteRunOpts

#Region " Windows Form Designer generated code "
#End Region

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load

onesite = Sites(0)
onesite.set_by_Load_code = "SET"

Dim CallDaSub As delegTestSub = New delegTestSub(AddressOf testsub)

Me.Invoke(CallDaSub, New Object() {onesite})
'
''> onesite.set_by_TestSub is Nothing here !?!?!?!
'
End Sub

Private Sub testsub(ByRef site As SiteRunOpts)
site.set_by_TestSub_code = "SET"
End Sub
End Class

(You have to use the debugger to see what I am saying. Also, sorry if this
gets posted twice; I am having a problem with OE.)

Bob
 
S

Stephany Young

But you are not modifying the reference to Sites(0). After the call it is
still a reference to the same object. Therefoe you shouldn't be using ByRef
in this case.

To do what you want to do, SiteRunOpts needs to be a Class instead of a
Structure.

And, the first element of your array of SiteRunOpts needs to be
instantiated:

Public Shared Sites As SiteRunOpts() = New SiteRunOpts(0) {New
SiteRunOpts}
 
T

Tom Shelton

If I have a structure and I pass it ByRef to a Subroutine via a Delegate,
any changes to the structure are not seen by the caller. This sure seems
unfortunate to me and I would welcome any explanation of why this is
reasonable behavior. In any event I think that the Delegate documentation
should point this out in bold face type. The following code illustrates the
problem:

Option Explicit On
Option Strict On

Public Class Form1
Inherits System.Windows.Forms.Form

Delegate Sub delegTestSub(ByRef si As SiteRunOpts)

Structure SiteRunOpts
Public set_by_Load_code As String
Public set_by_TestSub_code As String
End Structure

Public Shared Sites(0) As SiteRunOpts

Public onesite As SiteRunOpts

#Region " Windows Form Designer generated code "
#End Region

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load

onesite = Sites(0)
onesite.set_by_Load_code = "SET"

Dim CallDaSub As delegTestSub = New delegTestSub(AddressOf testsub)

Me.Invoke(CallDaSub, New Object() {onesite})
'
''> onesite.set_by_TestSub is Nothing here !?!?!?!
'
End Sub

Private Sub testsub(ByRef site As SiteRunOpts)
site.set_by_TestSub_code = "SET"
End Sub
End Class

(You have to use the debugger to see what I am saying. Also, sorry if this
gets posted twice; I am having a problem with OE.)

Bob

Option Strict On
Option Explicit On

Imports System

Module Module1
Sub Main()
Dim tc As New TestClass()
tc.TestIt()
Console.WriteLine(tc.onesite.set_by_Load_code)
Console.WriteLine(tc.onesite.set_by_TestSub_code)
End Sub

Class TestClass
Delegate Sub delegTestSub(ByRef si As SiteRunOpts)


Structure SiteRunOpts
Public set_by_Load_code As String
Public set_by_TestSub_code As String
End Structure


Public Shared Sites(0) As SiteRunOpts
Public onesite As SiteRunOpts

Public Sub New()
onesite = Sites(0)
onesite.set_by_Load_code = "SET"

End Sub
Public Sub TestIt()
Dim CallDaSub As delegTestSub = New delegTestSub(AddressOf
testsub)
CallDaSub.Invoke(onesite)
End Sub

Private Sub testsub(ByRef site As SiteRunOpts)
site.set_by_TestSub_code = "SET"
End Sub

End Class
End Module

This works fine for me... Or were you expecting your Sites(0) to be
updated as well? Because that won't happen. Structures are value
types, and when you assign them, a copy is made. So, onesite does NOT
refer to Sites(0). If you want that sort of behavior, then you need
to make your siteopts a class.
 
E

eBob.com

Stephany Young said:
But you are not modifying the reference to Sites(0). After the call it is
still a reference to the same object. Therefoe you shouldn't be using
ByRef in this case.
No, I am modifying onesite. I want the modification to be seen by the
caller and that is why I am using ByRef.
To do what you want to do, SiteRunOpts needs to be a Class instead of a
Structure.
Yes, using a Class solves the problem. But I still think that in my code
the change made to the structure in the testsub subroutine should be seen by
the caller.
And, the first element of your array of SiteRunOpts needs to be
instantiated:
Well ... I am sure not a VB.Net or OOP expert, but my reading of the doc
says otherwise. Quoting from the doc ...
"To create an instance of a CLASS, use the New keyword. UNLIKE VALUE TYPES,
such as Integer and Double, objects are reference types, and you must
explicitly create them before you can use them."
 
E

eBob.com

Stephany Young said:
But you are not modifying the reference to Sites(0). After the call it is
still a reference to the same object. Therefoe you shouldn't be using
ByRef in this case.
No, I am modifying onesite. I want the modification to be seen by the
caller and that is why I am using ByRef.
To do what you want to do, SiteRunOpts needs to be a Class instead of a
Structure.
Yes, using a Class solves the problem. But I still think that in my code
the change made to the structure in the testsub subroutine should be seen by
the caller.
And, the first element of your array of SiteRunOpts needs to be
instantiated:
Well ... I am sure not a VB.Net or OOP expert, but my reading of the doc
says otherwise. Quoting from the doc ...
"To create an instance of a CLASS, use the New keyword. UNLIKE VALUE TYPES,
such as Integer and Double, objects are reference types, and you must
explicitly create them before you can use them."
 
T

Tom Shelton

But you are not modifying the reference to Sites(0). After the call it is
still a reference to the same object. Therefoe you shouldn't be using ByRef
in this case.

He should if he wants to modify a structure - it's a value type.
To do what you want to do, SiteRunOpts needs to be a Class instead of a
Structure.

Why? Sometimes a structure is what you want (value type sematics -
like integer, double, etc). Most of the time, you probably really
want a reference type (class) - but, not always.
And, the first element of your array of SiteRunOpts needs to be
instantiated:

Not with a structure, you don't.
 
E

eBob.com

Tom Shelton said:
Option Strict On
Option Explicit On

Imports System

Module Module1
Sub Main()
Dim tc As New TestClass()
tc.TestIt()
Console.WriteLine(tc.onesite.set_by_Load_code)
Console.WriteLine(tc.onesite.set_by_TestSub_code)
End Sub

Class TestClass
Delegate Sub delegTestSub(ByRef si As SiteRunOpts)


Structure SiteRunOpts
Public set_by_Load_code As String
Public set_by_TestSub_code As String
End Structure


Public Shared Sites(0) As SiteRunOpts
Public onesite As SiteRunOpts

Public Sub New()
onesite = Sites(0)
onesite.set_by_Load_code = "SET"

End Sub
Public Sub TestIt()
Dim CallDaSub As delegTestSub = New delegTestSub(AddressOf
testsub)
CallDaSub.Invoke(onesite)
End Sub

Private Sub testsub(ByRef site As SiteRunOpts)
site.set_by_TestSub_code = "SET"
End Sub

End Class
End Module

This works fine for me... Or were you expecting your Sites(0) to be
updated as well? Because that won't happen. Structures are value
types, and when you assign them, a copy is made. So, onesite does NOT
refer to Sites(0). If you want that sort of behavior, then you need
to make your siteopts a class.
Hi Tom, As always thanks for your prompt and helpful response. Your code
works fine for me too. And no I was not expecting Sites(0) to be updated.
But I am still confused as to why, in my code, onesite is not updated. It
is at this point academic as I have changed my structure to a class and
moved on. But I sort of get into these issues. I mean, here I am running
valid code, compiled with Strict On and Explicit On, and following all
relevant documentation which I have been able to find, and the code does not
work (IMHO). Well, as I said I have moved on. But if you or anyone else
looking at this has any idea why my code code does not work (i.e. the update
to onesite in testsub is not seen by the caller) I would sure be interested.
Thanks again, Bob
 
T

Tom Shelton

Hi Tom, As always thanks for your prompt and helpful response. Your code
works fine for me too. And no I was not expecting Sites(0) to be updated.
But I am still confused as to why, in my code, onesite is not updated. It
is at this point academic as I have changed my structure to a class and
moved on. But I sort of get into these issues. I mean, here I am running
valid code, compiled with Strict On and Explicit On, and following all
relevant documentation which I have been able to find, and the code does not
work (IMHO). Well, as I said I have moved on. But if you or anyone else
looking at this has any idea why my code code does not work (i.e. the update
to onesite in testsub is not seen by the caller) I would sure be interested.
Thanks again, Bob- Hide quoted text -

- Show quoted text -

I'm not sure... I restructed mine a little - just to be console app,
but other then that I pretty much just copied and pasted the relevant
parts of your code. Maybe I'll try and run it as is latter.
 
T

Tom Shelton

I'm not sure... I restructed mine a little - just to be console app,
but other then that I pretty much just copied and pasted the relevant
parts of your code. Maybe I'll try and run it as is latter.

Ok... I have it figured out. The problem is Me.Invoke. The parameter
is array of object - which means that a boxing operation occurs, which
does not occure with a reference type, causing a new copy to be made
of the object. If you change the line to:

CallDaSub.Invoke(onesite)

Then everything works as expected. Once I debuged through the code -
I realized what was going on :)
 
E

eBob.com

Tom Shelton said:
Ok... I have it figured out. The problem is Me.Invoke. The parameter
is array of object - which means that a boxing operation occurs, which
does not occure with a reference type, causing a new copy to be made
of the object. If you change the line to:

CallDaSub.Invoke(onesite)

Then everything works as expected. Once I debuged through the code -
I realized what was going on :)
In the real application what I need to do is to add a control to the UI
form, and that of course has to happen on the thread associated with the UI
form. So I think (and I am in over my head here) that I have to use the
form's Invoke method. And the official doc I have found for using the
form's Invoke method says that the only way to pass arguments is to pass an
Object which is an array of objects. So I think I have no choice about the
call to the form's Invoke method. (Except, as we have discussed, that I
don't have to use a structure, I can use a class object, which is what I am
doing now.) I hadn't encountered boxing before. I just read a bit about it
in Balena and he refers to "unboxing" but does not say under what
circumstances it occurs. Apparently not on the return from the Form Invoke
method.

Thanks very much for helping me to resolve this mystery. I've learned a
lot.

Bob
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top