Resources

VB6 vs VB.NET language - Classes and ActiveX components


Unless otherwise stated, VB Migration Partner fully supports all the Visual Basic 6 features and keywords mentioned in this page. For more information, please read the manual and the knowledge base section.





Classes and ActiveX components

Property procedures

A VB6 property is defined by means of its Property Get, Property Let, and Property Set procedures. These procedures are converted into a single Property…End Property VB.NET block, which can optionally be marked with the ReadOnly or WriteOnly keywords if one of the blocks is omitted. During the conversion it is also necessary to account for different scopes of the Property Get block and the Property Let (or Set) block. For example, consider the following VB6 code:
        Public Property Get ID() As Integer
            ID = m_ID
        End Property
        
        Public Property Get Name() As String
            Name = m_Name
        End Property
        
        Friend Property Let Name(ByVal newValue As String)
            m_Name = newValue
        End Property
This is how the property must be translated to VB.NET:
        Public ReadOnly Property ID() As Short
            Get
                Return m_ID
            End Get
        End Property
        
        Public Property Name() As String
            Get
                Return m_Name
            End Get
            Friend Set(ByVal newValue As String)
                m_Name = newValue
            End Set
        End Property

Properties with both Let and Set procedures

A VB6 property of Variant type can appear in both a Property Let and a Property Set procedure. VB.NET’s Property…End Property block supports only one “setter” block, which must merge code from both original blocks. In most cases you can (and should) simplify the code that is generated by converting and merging the VB6 code verbatim. For example, given the following VB6 code:
        Property Get Owner() As Variant
            If IsObject(m_Owner) Then
                Set Owner = m_Owner
            Else
                Owner = m_Owner
            End If
        End Property
        
        Property Let Owner(ByVal newValue As Variant)
            m_Owner = newValue
        End Property
        
        Property Set Owner(ByVal newValue As Variant)
            Set m_Owner = newValue
        End Property
VB Migration Partner converts this code to VB.NET as follows:
        Public Property Owner() As Object
            Get
                Return m_Owner
            End Get
            Friend Set(ByVal newValue As Object)
                m_Owner = newValue
            End Set
        End Property
Also, notice that the original Property Let and Property Set procedures might have different visibility – Friend and Private, for example – therefore you have to choose the “broader” visibility (Friend, in this case) when you merge them into a single “setter” block.

Optional parameters in Property procedures

In VB6 it is legal to have a Property Get and a Property Let (or Set) block whose parameters differ for the Optional keyword, as in the following example:
    Dim m_Value(10) As String
    
    Property Get Value(ByVal index As Long) As String
        Value = m_Value(index)
    End Property
    
    Property Let Value(ByVal Optional index As Long, ByVal newValue As String)
        m_Value(index) = newValue
    End Property
(Notice that this is the only case in which a non-Optional argument can follow an Optional parameter.) In VB.NET the “getter” and “setter” blocks of a Property share the same parameters, therefore they can’t differ for the Optional keyword. In this case, VB Migration Partner uses the Optional keyword for the parameter:
    Dim m_Value(10) As String
    
    Property Value(ByVal Optional index As Integer = 0) As String
        Get
            Return m_Value(index)
        End Get
        Set (ByVal newValue As String)
            m_Value(index) = newValue
        End Set
    End Property
An even more intricate case occurs when the Property Get and Property Set block differ for the default value of an optional parameter, as in:
    Dim m_Value(10) As String
    
    Property Get Value(ByVal Optional index As Long = 0) As String
        Value = m_Value(index)
    End Property
    
    Property Let Value(ByVal Optional index As Long = -1, ByVal newValue As String)
        m_Value(index) = newValue
    End Property
While this syntax admittedly makes little sense, it is perfectly legal in VB6. However, there is no way to convert this syntax correctly to VB.NET, thus VB Migration Partner flags it with a migration warning.

Initialize event

VB.NET doesn’t support the Initialize event in classes, forms, and user controls. Any action that needs to be performed when an instance of the class is created should be moved to the class’s constructor.

Terminate event

VB.NET doesn’t support the Terminate event in classes, forms, and user controls. The VB.NET element that is closest to the Terminate event is the Finalize method, but the two aren’t equivalent. The problem is that VB.NET (and all .NET Framework languages, for that matter) doesn’t support the so-called deterministic finalization, which means that .NET objects aren’t destroyed when the last reference to them is set to Nothing. This difference causes unpredictable runtime errors after the migration, unless the developer is very careful in how objects are destroyed; in general, the amount of code that must be written to work around the problem isn’t negligible.

VB Migration Partner can generate such code if the AutoDispose pragma is used.

Default properties (definitions)

In VB6 you can define a field, a property, or a method as the default member of a class. The most common cases of default members are properties exposed by controls, such as the Text property of the TextBox control or the Caption property of the Label control. VB.NET supports neither default fields nor default methods; only default properties are supported and, more important, only properties that have one or more arguments (e.g. the Item property of a Collection). Here’s how you can define a default property in VB.NET:
        Default Property Item(ByVal index As Integer) As String
            Get
                Return m_Items(index)
            End Get
            Set(ByVal value As String)
                m_Items(index) = value
            End Set
        End Property

Default properties (references)

VB Migration Partner correctly resolves reference to default properties if the variable is strongly-typed. For example, consider the following VB6 method:
        Sub UppercaseText(ByVal tb As TextBox)
            tb = UCase(tb)
        End Sub
VB.NET doesn’t support default parameterless properties, therefore you must explicitly reference the default property:
        Sub UppercaseText(ByVal tb As TextBox)
            tb.Text = UCase(tb.Text)
        End Sub
The actual problem with default parameterless properties becomes apparent when the variable is late-bound, as in this case:
        Sub UppercaseText(ByVal ctrl As Object)
            ctrl = UCase(ctrl)
        End Sub
In this case, VB Migration Partner can correctly resolve the default property at runtime if you enable the corresponding feature with the DefaultMemberSupport pragma.

Default functions

In VB6 you can define a method as the default member of a class, whereas VB.NET supports only default properties and only if the property takes one or more arguments. For this reason, you should turn the Function into a Readonly Property block and mark it with the Default keyword.

VB Migration Partner automatically does this replacement.

Default members and COM clients

If a VB6 class contains a default member and the class is exposed to COM clients, when you translate the class to VB.NET you should mark the default member – be it a field, a property, or a method – with a System.Runtime.InteropServices.DispID attribute, as in this example:
        <System.Runtime.InteropServices.DispID(0)> _ 	
        Public Name As String 

Member description

You can decorate a VB6 class or class member with a description; such a description appears when the class is explored by means of the VB6 Object Browser. If the class is a user control and the member is a property, the description appears also in the property grid at design time. To implement the same support in a VB.NET class you must convert VB6’s Description attribute to the equivalent XML comment (to display the description in the object browser) and to a System.ComponentModel.Description attribute:
        ''' <summary> 
        ''' Name of the widget
        ''' </summary>
        <System.ComponentModel.Description("Name of the widget")> _ 
        Public Property Name() As String 
            ' …
        End Property

Classes and interfaces

VB6 has no notion of interfaces: you define an interface by authoring a VB6 class with one or more empty methods or properties, then use the class’s name in an Implements clause at the top of another class elsewhere in the same project. (If the class that defines the interface is public then the class can implement the interface can reside in a different project.) In VB.NET you have to render interfaces with an explicit Interface…End Interface block.

In some rare cases, however, a VB6 class is used to define an interface and is also instantiated: in the converted VB.NET program such a class must be rendered as two distinct types: an interface and a concrete class. Consider the following VB6 class named IAddin:
        Public Property Get Name() As String
            ' no code here
        End Property
        
        Public Sub Connect(ByVal app As Object)
            ' no code here
        End Sub
By default, VB Migration Partner converts this code into an Interface block plus a Class block:
        Interface IAddin
            ReadOnly Property Name()  As String
            Sub Connect(ByVal app As Object)
        End Interface
        
        Class IAddinClass
            Implements IAddin
        
            Public ReadOnly Property Name() As String Implements IAddin.Name
                Get
                    ' no code here
                End Get
            End Property
            
            Public Sub Connect(ByVal app As Object) Implements IAddin.Connect
                ' no code here
            End Sub
        End Class
You can use the ClassRenderMode pragma to tell VB Migration Partner that only the Interface block should be generated.

Fields inside interfaces

A VB6 interface – more precisely, a VB6 class that is used to define an interface – can include one or more public class-level fields. Such fields become part of the interface and must be accounted for by classes that implement the interface, typically by including a Property Get and Property Let (or Set) pair of procedures. VB.NET interfaces can’t include fields, therefore the VB6 must be transformed into a property when the class is converted into an Interface…End Interface block. For example, consider the following VB6 class named IAddin:
        Public Name As String
        
        Public Sub Connect(ByVal app As Object)
            ' no code here
        End Sub
VB Migration Partner converts it to VB.NET as follows:
        Interface IAddin
            Property Name()  As String
            Sub Connect(ByVal app As Object)
        End Interface
        
        Class IAddinClass
            Implements IAddin
            
            Private Name_InnerField As String
            
            Public Property Name() As String Implements IAddin.Name
                Get
                    Return Name_InnerField
                End Get
                Set(ByVal value As String)
                    Name_InnerField = value
                End Set
            End Property
            
            Public Sub Connect(ByVal app As Object) Implements IAddin.Connect
                ' no code here
            End Sub
        End Class

Collection classes

VB6 collection classes require that a property or method returns the class’s enumerator object, which the client application can use to iterate over all the elements of the collection. (This object is implicitly requested and used when a For Each loop is encountered.) This method – which is usually named NewEnum and is usually hidden - must be marked with DispID attribute equal to -4. The enumerator object returned by the NewEnum method must implement the IEnumVariant interface. However, you can’t implement such an interface with VB6, therefore VB6 collection classes typically return the enumerator object of an inner collection. The following code represents the minimal implementation of a VB6 collection class named Widgets:
        ' The private collection used to hold the real data
        Private m_Widgets As New Collection
        
        ' Return the number of items the collection
        Public Function Count() As Long
            Count = m_Widgets.Count
        End Function
        
        ' Return a Widget item from the collection
        ' This item is marked with DispID=0 to make it the default member of the class
        Public Function Item(index As Variant) As Widget
            Set Item = m_Widgets.Item(index)
        End Function
        
        ' Implement support for enumeration (For Each)
        ' this member is marked with DispID=-4 and is usually hidden
        Function NewEnum() As IUnknown
            ' delegate to the private collection
            Set NewEnum = m_Widgets.[_NewEnum]
        End Function
VB.NET collection classes must implement the IEnumerable interface and are expected to return an enumerator object through the only method of this interface, GetEnumerator. In turn, VB.NET Enumerator objects must implement the IEnumerator interface and its MoveNext, Reset, and Current members.
        Class Widgets

            ' The private collection used to hold the real data
            Private m_Widgets As New Collection
        
            ' Return the number of items the collection
            Public Function Count() As Integer
                Return m_Widgets.Count()
            End Function
            
            ' Return a Widget item from the collection
            Public Function Item(ByRef index As Object) As Widget
                Return m_Widgets.Item(index)
            End Function
            
            ' Implement support for enumeration (For Each)
            Public Function NewEnum() As Object
                Return m_Widgets.GetEnumerator()
            End Function
            
        End Class
VB Migration Partner correctly converts the NewEnum member into the IEnumerable.GetEnumerator method, even if NewEnum was originally defined as a property. As explained above, the NewEnum member returns the inner collection’s enumerator, therefore the resulting VB.NET collection class never needs to implement the IEnumerator interface. Therefore, VB.NET collection classes converted from VB6 work exactly as expected.

Public COM classes

A public VB6 class defined in an ActiveX EXE or DLL project is visible to COM clients, which can instantiate the class by either a New keyword or the CreateObject method. After the conversion to VB.NET the class must be marked with a ComVisible attribute to make explicitly visible to existing COM clients, plus a ProgID attribute that contains the original name of the class:
        <System.Runtime.InteropServices.ComVisible(True)> _ 
        <System.Runtime.InteropServices.ProgID("SampleProject.Widget)> _ 
        Public Class Widget 
            ' ...
        End Class

PublicNotCreatable classes

Public VB6 classes whose Instancing attribute is set to 1-PublicNotCreatable must be converted to public VB.NET classes whose constructor has Friend scope, so that the class can’t be instantiated from outside the project where the class is defined.
        Public Class Widgets
            Friend Sub New()
            End Sub
            
            …
        End Class

SingleUse classes

An ActiveX EXE project can define one or more SingleUse and Global SingleUse public classes. SingleUse classes differ from the more common MultiUse classes in that a new instance of the ActiveX process is created any time a client requests an instance of the class. The .NET Framework doesn’t support anything similar to SingleUse classes and it isn’t easy to simulate this feature under VB.NET; moreover, having a distinct process for each instance of a class impedes scalability, therefore it is recommended that you revise the overall architecture so that the application doesn’t depend on SingleUse behavior.

VB Migration Partner ignores the SingleUse attribute and converts SingleUse classes to regular COM-visible VB.NET classes.

Global classes

VB6 supports Global SingleUse and Global MultiUse classes inside ActiveX EXE and DLL projects. (ActiveX DLL projects can’t contain SingleUse classes, though.) There is nothing like global classes in VB.NET, therefore all such classes are handles as regular classes, but the client application instantiates and uses a default instance for each global class, and use it to invoke methods and properties.

VB Migration Partner can convert the global class to a class that contains only Shared members; VB Migration Partner can also convert the global class to a Visual Basic module, if an opportune ClassRenderMode pragma is used.

DataEnvironment classes

VB.NET doesn’t support DataEnvironment classes. Notice that VB6 supports default instances of DataEnvironment classes, therefore the converted VB.NET application must instantiate such global instances as necessary.

VB Migration Partner converts DataEnvironment classes to special VB.NET classes that inherit from the VB6DataEnvironment base class, and correctly handles default instances; however, it doesn’t converts advanced features such as grouping, relations, and hierarchical DataEnvironment classes.

PropertyPages

The .NET Framework and VB.NET don’t support property pages.

VB Migration Partner converts VB6 property pages to .NET user controls; developers should then write the plumbing code to use and display the user control as appropriate.

UserDocuments

The .NET Framework and VB.NET don’t support user documents.

VB Migration Partner converts VB6 user documents to .NET user controls; developers should then write the plumbing code to use and display the user control as appropriate.

Sub Main in ActiveX DLL projects

If an ActiveX DLL project contains a Sub Main method, the Main method is guaranteed to be executed before any class in the DLL is instantiated. VB6 developers can use this feature to read configuration files, open database connections, and so forth. Conversely, the Sub Main method is ignored inside a DLL authored in VB.NET, therefore code must be written to ensure that initialization chores be performed before any .NET object is created.

VB Migration Partner ensures that the Sub Main is executed before any class in the DLL is instantiated. This is achieved by adding a static constructor to all public classes in the DLL, as in this code:
        Public Class Widget
            Shared Sub New()
                EnsureVB6ComponentInitialization()
            End Sub
            …
        End Class
where the EnsureVB6ComponentInitialization method is a method that invokes the Sub Main method if Widget is the first class being instantiated.

MTS components

A public VB6 class defined in an ActiveX DLL project can be made a transactional MTS/COM+ component by setting its MTSTransactionMode attribute to a value other than 0-NotAnMTSObject. VB.NET classes don’t support this attribute: instead, the VB.NET class must inherit from the ServicedComponent base class and be tagged with the Transaction attribute whose argument specifies the required transaction level:
        Imports System.EnterpriceServices
        
        <Transaction(TransactionIsolationLevel.Required)> _
        Public Class MoneyTransfer
            Inherits ServicedComponent
            ' ...
        End Class

ObjectControl interface

MTS/COM+ components authored in VB6 can implement the ObjectControl interface, which consists of the following three methods: Activate, Deactivate, CanBePooled. VB.NET components that run under COM+ must not implement the ObjectControl interface; instead, they must override the Activate, Deactivate, and CanBePooled methods that they inherit from the System.EnterpriseServices.ServicedComponent base class.

VB Migration Partner automatically converts ObjectControl methods into the corresponding VB.NET overrides.

IObjectConstruct interface

MTS/COM+ components authored in VB6 can grab the construction string defined in Component Services applet by implementing the IObjectConstruct interface, which consists of just one method, Construct. This method receives an object argument, whose ConstructString property returns the construction string:
        Private Sub IObjectConstruct_Construct(Byval pCtorObj As Object)
            Dim connStr As String
            connStr = pCtorObj.ConstructString
            ' ...
        End Sub
VB.NET components that run under COM+ must not implement the IObjectConstruct interface; instead, they must override the Construct method that they inherit from the System.EnterpriseServices.ServicedComponent base class; the only argument that this method receives is the construction string:
        Protected Overrides Sub Construct(Byval connStr As String)
            ' ...
        End Sub

Persistable classes

Public VB6 classes in ActiveX EXE and DLL projects can be made persistable, by setting their Persistable attribute to 1-Persistable. VB.NET doesn’t support the Persistable attribute: a VB.NET class can be persisted to file - or passed by value to an assembly living in a different AppDomain – if the class is marked with the <Serializable> attribute.

VB Migration Partner converts persistable VB6 classes into VB.NET classes that are marked with the <Serializable> attribute and that implement the ISerializable interface.

InitProperties, WriteProperties, and ReadProperties events

Persistable VB6 classes can handle the InitProperties, WriteProperties, and ReadProperties events, which fire – respectively – when the class is instantiated, when the COM infrastructure needs to store the object’s state somewhere, and when the object is asked to restore a previous state. These events aren’t supported by the .NET Framework: a VB.NET classes requiring custom serialization must implement the ISerializable interface and therefore implement the GetObjectData method and the special constructor that this interface implies.

VB Migration Partner extracts the code from the InitProperties, WriteProperties, and ReadProperties events and uses it inside GetObjectData method and the special constructor implied by the ISerializable interface.

ADO data source and data consumer classes

VB6 allows you to create databinding-aware classes, none of which are supported by VB.NET. More precisely, in VB6 you can create
  • ADO data source classes or user controls (by setting the DataSourceBehavior attribute to 1-vbDataSource); for example you might create a custom version of the AdoDC control and bind other controls to it.
  • ADO data consumer classes or user controls, that can be bound to an AdoDC control, a DataEnvironment object, an ADO Recordset object, or an ADO data source object. Two different flavours of data consumer classes are supported: simplex-bound (DataBindingBehavior=1-vbSimpleBound) and complex-bound (DataBindingBehavior=2-vbComplexBound). For example, a textbox-like user control might be defined as a simple-bound consumer class, because it displays data taken from a single record exposed by the data source, whereas a grid-like user control might be defined as a complex-bound class, because it displays data from multiple records.
VB Migration Partner supports data source classes and simple-bound data consumer classes and user controls (but not complex-bound data consumer classes and user controls).

AddIn classes

VB6 addin classes aren’t supported by VB.NET. Microsoft Visual Basic 6 and Microsoft Visual Studio 2005’s object models are too different for this feature to be migrated automatically. (Manual translation isn’t exactly easier either.)

VB Migration Partner doesn’t support addin clases.

WebClass components

VB.NET support WebClass components. VB6 applications that used WebClass components should be converted to ASP.NET for better speed, more power, and easier maintenance.

The Upgrade Wizard provides a limited support to WebClass components; VB Migration Partner doesn’t support these components at all.

DHTML Page components

VB.NET doesn’t support DHTML Page components.

VB Migration Partner drops these components when converting VB6 applications, and emits one migration warning for each DHTML Page component.






Follow Francesco Balena on VB6 migration’s group on

LinkedIn





Read Microsoft Corp’s official case study of a VB6 conversion using VB Migration Partner.




Code Architects and its partners offers remote and onsite migration services.

More details




Subscribe to our free newsletter for useful VB6 migration tips and techniques.

newsletter



To learn more about your VB6 applications, run VB6 Analyzer on your source code and send us the generated text file. You will receive a detailed report about your VB6 applications and how VB Migration Partner can help you to quickly and effectively migrate it to .NET.

Get free advice



A fully-working, time-limited Trial Edition of VB Migration Partner allows you to test it against your actual code

Get the Trial




The price of VB Migration Partner depends on the size of the VB6 application, the type of license, and other factors

Request a quote




Migrating a VB6 application in 10 easy steps

Comparing VB Migration Partner with Upgrade Wizard

Migration tools: Feature Comparison Table

All whitepapers