Previous | Index | Next 

[HOWTO] Ensure that properties with both Property Let and Property Set are translated correctly

In VB6 you can define a property using as many as three accessors: Property Get, Property Let, and Property Set. VB Migration Partner automatically converts these properties correctly in most cases, but in some circumstances it is necessary to take additional steps to preserve functional equivalence.

For starters, let’s consider a very simple VB6 property that uses three accessors:

        ' (inside the Widget class)
        Private m_MyData As Variant

        Property Get MyData() As Variant
            If Not IsObject(m_MyData) Then
                MyData = m_MyData
            Else
                Set MyData = m_MyData
            End If
        End Property
  
        Property Let MyData(ByVal newValue As Variant)
            m_MyData = newValue
            ProcessDataValue newValue    ' (ProcessDataValue is defined elsewhere)
        End Property
  
        Property Set MyData(ByVal newValue As Variant)
            Set m_MyData = newValue
        End Property

The key point in the above code is that the Property Let and Property Set procedures contain slightly different code, therefore it is essential that the converted VB.NET code preserves the ability to discern between “let” assignments and “set” assignments. This is how VB Migration Partner usually converts this code:

        Private  m_MyData As Object

        Property MyData() As Object
            Get
                ' *** VB Migration Partner has simplified this code
                Return m_MyData
            End Get
		
            Set(ByVal newValue As Object)
                If Not IsObject6(newValue) Then
                    m_MyData = newValue
                    ProcessDataValue(newValue) ' (ProcessDataValue is defined elsewhere)
                Else
                    m_MyData = newValue
                End If
            End Set
        End Property

Next, let’s consider a piece of code that assigns the MyData property:

        Sub Main()
            Dim rs As New ADODB.Recordset, par As New  ADODB.Parameter
            Dim wi As New Widget
            ' ... init the recordset here
            wi.MyData = par
            Set wi.MyData = par
            ' ...
            wi.MyData = rs
            Set wi.MyData = rs
        End  Sub 

and the VB.NET code that both the Upgrade Wizard and VB Migration Partner generate by default:

        Sub Main()
            Dim rs As New ADODB.Recordset
            ' ... init the recordset here
            MyData = par.Value  ' ** resolution of default property
            MyData = par
            ' ...
            MyData = rs.Fields  ' ** resolution of default property
            MyData = rs
        End Sub

As you see, both the Upgrade Wizard and VB Migration Partner automatically append the default member of the objects that are passed to the property by means of “let” statements. This is where the undocumented VB6 behavior comes into play.

Unfortunately, appending the default member isn’t always sufficient to have VB.NET behave exactly like VB6. In fact, in this assignment

        MyData = rs.Fields  ' ** resolution of default property

the value received by the Set block of MyData is an object (the Fields collection), hence the Else portion of the block will be executed, which is a mistake because this code originates from a “let” assignment.

To further complicate matters, VB6 has an undocumented behavior that, in some cases, must be accounted form. In fact, a VB6 Property Let does not receive the object’s default member. Instead, it receives a reference to the actual object being assigned (par and rs, in previous case). You can easily prove this point by adding a test to the original VB6 code:

        Property Let MyData(ByVal newValue As Variant)
            Debug.Print TypeName(newValue)   ' displays "Parameter" and  "Recordset"
            '...
        End Property

You might have expected that the Debug.Print name would display “Variant” and “Fields” – that is, the type of the default members of the Parameter and Recordset classes, respectively – but this isn’t the case. The actual output proves that the VB6 Property Let is assigned the actual Parameter and Recordset objects.

To recap, there are two problems in exactly replicating the VB6 behavior under VB.NET:

  • If an object is assigned to a property in a “let” statement and the default property of that object is itself an object, then the VB.NET code that VB Migration Partner generates for the property – the test using IsObject6 method – will mistakenly execute the code that was originally in the Property Set procedure.
  • If the code inside the Property Let procedure used the newValue parameter to access any property or method other than the default member then the default member expansion carried out by VB Migration Partner in the assignment statement won’t work correctly, because the “set” block of the VB.NET property receives the wrong object reference.

Admittedly, this case isn’t frequent because – usually – the code in Property Let procedures only access the object’s default member. This is why the code generated by the Upgrade Wizard and by VB Migration Partner is OK in the vast majority of cases. In fact, we and our customers have migrated dozens millions lines of VB6 code without any problem, until one of our customers made us notice these undocumented details.

Starting with version 1.10.04, VB Migration Partner is able to generate code that works around both the above mentioned issues. The only thing you must do is flagging the property (or properties) in question with the PreservePropertyAssignmentKind pragma, as in this code:

        '## MyData.PreservePropertyAssignmentKind

Alternatively, you can use this pragma at the project- or file-level, in which case the pragma will affect all the properties under its scope. More precisely, it affects all the properties that have both the Property Let and Property Set accessors and in which the argument for the Property Let accessor is a Variant or object type.

If the above pragma is in effect, VB Migration Partner generates the following code:

        Private m_MyData As Object

        Property MyData(ByVal Optional _assignmentKind As PropertyAssignmentKind _
                    = PropertyAssignmentKind.Let) As Object
            Get
                ' *** VB Migration Partner has  simplified this code
                Return m_MyData
            End Get
            Set(ByVal newValue As Object)
                If _assignmentMode =  PropertyAssignmentKind.Let Then
                    m_MyData = newValue
                    ProcessDataValue(newValue)    ' (ProcessDataValue is defined elsewhere)
                Else
                    m_MyData = newValue
                End If
            End Set
        End Property
		
        Sub  Main()
            Dim rs As New ADODB.Recordset
            ' ... init the recordset here
            MyData = par        '  ** no resolution of default property
            MyData(PropertyAssignmentKind.Set) = par
            ' ...
            MyData = rs         ' ** no resolution of default property
            MyData(PropertyAssignmentKind.Set) = rs
        End Sub 

There are two important differences from the previous VB.NET code:

  • the definition of the MyData property contains an additional _assignmentKind parameter, which is used in the Set block to discern between “let” and “set” assignments
  • assignments to the property never expand the default member; instead, all “set” assignments specify the PropertyAssignmentKind.Set argument, to let the property know which VB6 keyword was originally used. (The PropertyAssignmentKind.Let argument is always omitted because it’s the default value for the parameter, a simple tricks that reduce code clutter.)

A final word: the issue described in this article is quite contorted and it takes a while to digest all this information. However, it is important to keep in mind that you should worry about this problem and the new PreservePropertyAssignmentKind pragma only if all the following conditions are met:

  1. if you have defined one or more Property with three accessors
  2. the code in the Property Let and Property Set accessor is substantially different and therefore you need to keep track whether assignments (in VB6) were performed by means of the Set keyword or an (implicit or explicit) Let keyword.
  3. your code contains one or more “let” statements that assign an object whose default member is also an object. (If you only assign objects whose default member is a scalar, the default test using IsObject6 method suffices to discern between “let” and “set” assignments.)

 

Previous | Index | Next