0

I've been trying to read up on possible methods of casting one of my class objects to another to "simplify" some of my programming going forward. Specifically, I have a class I've built for some new development to store and retrieve information to/from our database, but I have a "legacy" class that I still need to use to retrieve data from an older iteration of the database until all of the data has been migrated and none of the applications/systems are looking for information in that older database. Here's an example:

Public Class Customer
    Public Property customerID As Integer
    Public Property firstName As String
    Public Property middleName As String
    Public Property lastName As String
    Public Property businessName As String
    Public Property mailingAddress As Address
    Public Property homePhone As String
    Public Property workPhone As String
End Class

Public Class Address
    Public Property address1 As String
    Public Property address2 As String
    Public Property address3 As String
    Public Property city As String
    Public Property state As String
    Public Property zipCode As String
    Public Property zip4 As String
End Class

And the old class objects:

Public Class LegacyCustomer
    Public Property id As Int64 = -1
    Public Property type As String = String.Empty
    Public Property name As String = String.Empty
    Public Property subtype As Integer? = Nothing
End Class

Public Class LegacyAddress
    Public Property id As Int64
    Public Property type As String = String.Empty
    Public Property addr1 As String = String.Empty
    Public Property addr2 As String = String.Empty
    Public Property city As String = String.Empty
    Public Property state As String = String.Empty
    Public Property county As String = String.Empty
    Public Property zip As Integer? = Nothing
    Public Property zip4 As Integer? = Nothing
End Class

As you can see, there are several similar properties between the old and new objects in just the above example code, but not all of them are able to "match up" exactly with their counterparts. A couple of examples:

  • The zipCode property in the "new" Address class is of type String while the zip property in the "old" LegacyAddress class is of type Integer?.
  • Where the Customer class has a name broken into component parts (firstName, middleName, lastName, businessName), the LegacyCustomer class has all the parts combined into a single name property.
  • The LegacyCustomer class has a type and subtype property while these properties do not exist in the Customer class.
  • The customerID property of the Customer class will almost certainly be different than the id property of the LegacyCustomer class.

So, in my research, I've been looking into building a custom type conversion routine a la this answer by Anthony Pegram to the similar question, "cast class into another class or convert class to another" I don't believe it would be too incredibly challenging to use this method, but I wonder if I need to use the Narrowing or a Widening version of this method. I would assume the former, but I'm not certain and would appreciate some guidance.

Also, because the mailingAddress property of the Customer class is of another class type and there is no matching object in the LegacyCustomer class, I'm guessing I would need to possibly create a matching "placeholder" address property (of type LegacyAddress) into the LegacyCustomer object in order for this to work as intended/expected. Here's kinda what I envision (this is all untested "pseudo-code" for now):

Add a property to the LegacyCustomer object

Public Class LegacyCustomer
    'properties as defined above, plus
    Public Property address As LegacyAddress
End Class
Public Class Customer
    'properties as defined above
    
    Public Shared Narrowing Operator CType(ByVal cust As Customer) As LegacyCustomer
        Dim result As New LegacyCustomer
        
        With result
            If Not cust.businessName Is Nothing AndAlso Not String.IsNullOrEmpty(cust.businessName) Then
                .name = cust.businessName
            Else
                If Not cust.lastName Is Nothing AndAlso Not String.IsNullOrEmpty(cust.lastName) Then
                    .name = cust.lastName & "," & cust.firstName & " " & cust.middleName
                Else
                    .name = cust.firstName & " " & cust.middleName
                End If
            End If
            
            .name = .name.Trim()
            .type = "F"

            With .address
                .address1 = cust.mailingAddress.address1
                .address2 = cust.mailingAddress.address2

                If Not cust.mailingAddress.address3 Is Nothing AndAlso Not String.IsNullOrEmpty(cust.mailingAddress.address3) Then
                    .address2 = cust.mailingAddress.address2 & " " & cust.mailingAddress.address3
                End If

                .city = cust.mailingAddress.city
                .state = cust.mailingAddress.state

                If IsNumeric(cust.mailingAddress.zipCode) Then
                    .zip = TryCast(cust.mailingAddress.zipCode, Integer)
                End If

                If IsNumeric(cust.mailingAddress.zip4) Then
                    .zip4 = TryCast(cust.mailingAddress.zip4, Integer)
                End If

                .type = "A"
            End With
        End With
        
        Return result
    End Operator
End Class

Then, I'd probably have something similar in the LegacyCustomer class to convert it back the other way, although that may be a bit trickier.

I guess my actual question is in two parts:

  1. Is overriding the CType operation in this way the "most effective" method of converting these objects from one to another? and
  2. Am I correct in assuming that I'll need to use the Narrowing operator and not the Widening?
G_Hosa_Phat
  • 976
  • 2
  • 18
  • 38
  • 3
    Why not create an Interface, say ICustomer that both Customer and LegacyCustomer implement. Will stream line the type casting and saving having to make any placeholder properties for non-common properties like the mailing address – Hursey Apr 12 '21 at 21:22
  • That's an interesting idea I hadn't considered, @Hursey. Thank you for providing that perspective. My only hesitation on that is that I've already got the `Customer` class built and used in a significant way throughout my application as defined above. I realize it shouldn't be *too* difficult to change the definition to accommodate an `ICustomer` interface, but I'm kinda wondering if it would be "better" or "worse" than what I've proposed above. – G_Hosa_Phat Apr 12 '21 at 21:27
  • 1
    Being better or worse, that something I can't really answer. If I was in your situation though it's the direction I would be looking. Either that or straight inheritance – Hursey Apr 12 '21 at 21:42
  • 1
    I suggest to couple the implicit operator (narrowing here, since the old class structure cannot contain all possible values of the new one - the other way around if you define a operator to convert the old class to the new one) with a TypeConveter. In this case, the TypeConverter can make use of the operators to simplify the conversion (to make it more *linear*). To better understand what I mean, see the second snippet here: [Writing a custom converter for appsettings.json](https://stackoverflow.com/a/66545821/7444103) (C#, I hope you speak the language). – Jimi Apr 12 '21 at 22:36
  • Thank you, @Jimi. If I understand your suggestion correctly, you're talking about basically using the code I posted above and, in addition, implement a custom TypeConverter using JSON deserialization of the "host" object to handle those "similar" properties (*e.g.*, `Customer.zipCode` <=> `LegacyCustomer.zip`) in order to maintain additional data integrity. Is that correct? – G_Hosa_Phat Apr 13 '21 at 13:45
  • 1
    The JSON serialization is only relevant to that Q&A, which is used to maintain compatibility between two class models that handle JSON serialization. You don't need a JSON *mediator*, it's not relevant to your use case (not *forbidden*, just redundant). IMO, a custom TypeConverter to automate the conversion between your two Types is simpler. This just requires to decorate the old class with `[TypeConverter(typeof(YourConverter))]`, then get the converter as `var converter TypeDescriptor.GetConverter(typeof([Your Class]))` then `var result = converter.ConvertTo([object], typeof([New Class]))`. – Jimi Apr 13 '21 at 16:45
  • 1
    The implicit operators (though not strictly required) can be used to automate the conversion, casting one type to another with minimal effort (as you can see in the sample code I linked). This way, you have independent parts of code that perform only a single, specialized, task, so minimal changes are needed to adapt to a different conversion model. When the conversion is not needed, you just remove the decoration from the old class (or keep one un-decorated version for this use). – Jimi Apr 13 '21 at 16:48

0 Answers0