Casting Upwards

When binding business objects to a datagrid, often you have a need to display some information that is not directly exposed by the object itself.  Maybe it’s a calculated value, maybe it’s something nested deeper in the object.  When faced with this issue, there are a few different action paths you can take.  You can add extra read-only properties to your business object to support the extra view information.  You can create a new class that inherits from the class you are displaying and put the extra properties in there.  Or you can handle the CellFormatting event in the datagrid and change the displayed values manually.  One of the downsides of using a new derived class with extra properties is that you can’t cast a base class to it.  You could cast down to the base class, but no casting up.

Here is a technique that is closest to the second option listed above and side-steps the upcasting problem.  I dislike the first option because it clutters the business object with UI-specific code.  Going with option 2 is only slightly better, while you can populate the correct display-specific object and return it from your business logic layer, either you have to have a method that return the derived type, or you will have to cast it to its correct type in the UI.  Even then, your business layer still contains UI logic.

So, keeping things separated, the derived display-specific class should be defined in the UI layer.  This means it will have extra read-only properties for use with databinding.  The business layer will return the basic object(s), so it will be up to us to convert these to UI-friendly versions.  There are two problems with converting the object: not all the object state may be exposed via public properties, and those properties may contain logic.  It would be best to copy the object by its internal state – private variables.

On first thought, working with the private variables means the code must be inside the source object and the destination object.  This would be tedious to do, passing in the destination object, then sending the source object’s private variables to the destination so the destination object can manipulate its own private variables.  Yuck.  However, using Reflection, the job gets a whole lot easier.

Here’s a small class with a method to convert one class to another by copying its private and public fields.  The properties are intentionally excluded since they may contain logic that modifies the internal state.  You should use this technique with care and know exactly what it does and does not do.  Basically, it copies values from one instance of a class to another.  This is fine for simple classes, but it’s not going to resolve references for you.

Consider ClassA with a private field of type ClassB.  ClassB maintains a private variable with a reference to ClassA, so that it can manipulate all of its "parent’s" state and logic.  If you use this technique to cast ClassA to ClassAA, because you want an extra property to display some info from ClassB, you’re in for some fun results if you change some data in ClassAA.  This is because ClassB still has a reference to ClassA, not ClassAA.

Public Class UpCaster
    Shared Sub CastUp(ByVal sourceObj As Object, ByVal destinationObj As Object)
        Dim values As New Dictionary(Of String, Object)
        Dim props() As Reflection.FieldInfo 

        props = sourceObj.GetType.GetFields(Reflection.BindingFlags.NonPublic _
            Or Reflection.BindingFlags.Static _
            Or Reflection.BindingFlags.Instance _
            Or Reflection.BindingFlags.Public) 

        For Each p As Reflection.FieldInfo In props
            values.Add(p.Name, p.GetValue(sourceObj))
        Next 

        props = destinationObj.GetType.GetFields(Reflection.BindingFlags.NonPublic _
            Or Reflection.BindingFlags.Static _
            Or Reflection.BindingFlags.Instance _
            Or Reflection.BindingFlags.Public) 

        For Each p As Reflection.FieldInfo In props
            If values.ContainsKey(p.Name) Then p.SetValue(destinationObj, values(p.Name))
        Next 

    End Sub 

End Class