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:
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
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
Return m_MyData
End Get
Set(ByVal newValue As Object)
If Not IsObject6(newValue) Then
m_MyData = newValue
ProcessDataValue(newValue)
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
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
MyData = par.Value
MyData = par
MyData = rs.Fields
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
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)
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:
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
Return m_MyData
End Get
Set(ByVal newValue As Object)
If _assignmentMode = PropertyAssignmentKind.Let Then
m_MyData = newValue
ProcessDataValue(newValue)
Else
m_MyData = newValue
End If
End Set
End Property
Sub Main()
Dim rs As New ADODB.Recordset
MyData = par
MyData(PropertyAssignmentKind.Set) = par
MyData = rs
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:
- if you have defined one or more Property with three accessors
- 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.
- 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.)