Unexpected behavior

OzMaz

New member
Joined
Jul 28, 2022
Messages
4
Programming Experience
10+
Hi Folks,

I'm stumped!

I have three files: frmMain, modGeneral and cFloatingPoint.

modGeneral-----------------------
Public Structure sRegisters
Public X As cRegisters
Public Y As cRegisters
Public Z As cRegisters
Public T As cRegisters
End Structure

Sub Test()
...
End Sub

Public Registers As sRegisters

frmMain--------------------------
With Registers
.X = New cRegisters
.Y = New cRegisters
.Z = New cRegisters
.T = New cRegisters
End With

Call Test

cFloatingPoint---------------------------
Class cRegisters
...
End Class

###########################################
if I stop at Class test and look in Registers, all appears well. I have four variables: X, Y, Z and T and they are all initialised to cRegisters.
The odd thing is that if I'm in sub Test, the four variables show as nothing. Huh?

So the question is why?


Any help qould be appreciated.

Thanks.
 
It's a classic behavior difference between Structures (Value Types) and Classes (Reference Types) in Visual Basic (and other similar languages).
Why you're seeing this:

sRegisters is a Structure (Value Type): When you declare Public Registers As sRegisters in modGeneral, you are creating a variable Registers that directly holds the data of the sRegisters structure. Structures are allocated on the stack (typically), and when you assign a structure variable, you are copying the entire structure's data.

cRegisters is a Class (Reference Type): When you create a cRegisters object using New cRegisters, you are creating an instance of the class on the heap. The variables X, Y, Z, and T within your sRegisters structure hold references (pointers) to these objects on the heap.

The Problem: Passing Structures by Value (Default Behavior): When you call Call Test from frmMain, the Registers variable from modGeneral is being accessed. However, the way it's being accessed or potentially passed (even implicitly through module-level scope) is where the issue lies.
In frmMain, when you do:
VB.NET:
With Registers
    .X = New cRegisters
    .Y = New cRegisters
    .Z = New cRegisters
    .T = New cRegisters
End With

  1. You are initializing the X, Y, Z, and T members of the Registers structure in the context where this code is running. This Registers variable is the one declared in modGeneral.
    However, when you enter Sub Test, you are accessing the Registers variable also declared in modGeneral. The crucial point is how the system handles the Registers variable in these different contexts. While it's a module-level variable, the way structures are handled can lead to unexpected behavior if not careful.
    A likely scenario is that when Test is called, it's not operating on the exact same instance of the Registers structure that was modified in frmMain, even though it's the module-level variable. When you access Registers in Test, you are seeing the default initialized state of the sRegisters structure itself, where the object references (X, Y, Z, T) within it are still Nothing. The modifications made in frmMain were to a potentially different "view" or copy of that structure's data, or the references set in frmMain are not being maintained when Test is entered.
In essence: While Registers is a module-level variable, the structure sRegisters itself is a value type. The assignments made to its members (.X = New cRegisters, etc.) in frmMain might be happening on a copy of the Registers structure, or the references are not persisting as expected when the Test subroutine is executed. When Test accesses Registers, it's seeing the default state of the structure's members, which is Nothing for the object references.

To fix this, you generally have two main approaches:

Make sRegisters a Class instead of a Structure:
Class:
Public Class sRegisters
    Public X As cRegisters
    Public Y As cRegisters
    Public Z As cRegisters
    Public T As cRegisters

    ' Add a constructor to initialize the members
    Public Sub New()
        X = New cRegisters()
        Y = New cRegisters()
        Z = New cRegisters()
        T = New cRegisters()
    End Sub
End Class

' In modGeneral
Public Registers As sRegisters

' In frmMain, you would then just need to
' Registers = New sRegisters()
' The New() constructor handles the member initialization

  1. By making sRegisters a class, Registers will hold a reference to the sRegisters object on the heap. Both frmMain and Test will be working with the same instance of the sRegisters object and therefore the same references to the cRegisters objects.
  2. Ensure the Initialization Happens in a Shared/Accessible Location: If you must keep sRegisters as a structure, you need to ensure the initialization of its members happens in a way that the Registers variable in modGeneral is permanently updated. The code you showed in frmMain should theoretically do this for a module-level variable, but the behavior you're observing strongly suggests it's not happening as expected due to the value type nature of the structure. Using a class is the more idiomatic and less error-prone solution for this kind of linked data structure.
The reason it appears fine when you stop in frmMain is because you are inspecting the Registers variable immediately after you have performed the initializations on it in that scope. When you move to Test, the context might change in a way that the state you just set is not what is being accessed.
 
Back
Top