Reduction of object to its base class

rbulph

Junior Contributor
Joined
Feb 17, 2003
Messages
397
Is it possible to reduce an object down to its base class? To illustrate what I mean, the idea in the following code would be that the programme stops beeping after the button is pressed because the class2 object is reduced to a class1 object, which doesn't beep. But it doesn't work. Any ideas?

Visual Basic:
Public Class Form1
    Dim j1 As class1
    Dim j2 As class2

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Dim j2 As New class2(Me)

    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        'Get rid of the class2 object, I just need the class1 "core" of it now.
        j1 = CType(j2, class1)
        j2 = Nothing
    End Sub
End Class


Public Class class1
'This class sets the text of the form to the x position of the mouse when the mouse moves.
    Public WithEvents f As Form1
    Public v As Long

    Private Sub f_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles f.MouseMove
        f.Text = e.X
    End Sub
    Sub New(ByVal ff As Form1)
        f = ff
    End Sub
End Class


Public Class class2
'This class inherits class1 and beeps when the mouse moves over the form.
    Inherits class1
    Sub New(ByVal ff As Form1)
        MyBase.New(ff)
    End Sub

    Public gh As Long
    Private Sub f_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles f.MouseMove
        Beep()
    End Sub

End Class
 
Unless you want to create functions that do this for you, you are, for the most part, out of luck. You could create a constructor for Class1 that accepts an instance of Class2 and copies all of the fields, allowing you to create a Class1 from a Class2, but that is about as good as it gets unless you want to get your hands dirty with reflection.

Here is the problem: polymorphism. Suppose you have ClassA, which is used to copy a file, and ClassB, which inherits from ClassA to extend it and copy files from the internet. ClassA has two properties: Source and Target, which specify from where and to where the file is copied. ClassA also defines a virtual (overridable) function named PerformCopy which copies the file. ClassB simply overrides PerformCopy, adding logic that allows files to be copied from a URL in addition to a file path.

Now you create an instance of ClassB whose Source is "ftp://ftp.mysite.com/somedoc.html". If you call PerformCopy the file will be copied from the Internet to the hard drive. However, if you force the object to an instance of ClassA all of a sudden it loses the capability to copy files from the Internet and your object is broken. Values that were valid are no longer valid.

Besides that, in the grand scheme of object-oriented programming, that type of conversion doesn't make much sense. It is like wanting to be able to change your rice from regular rice to pork fried rice and back again. You can't do that. It is what it is. Reality doesn't work that way, and OOP mimics that aspect of reality. Sorry, but you are stuck with white rice.

That being said, I wrote this function in VB that uses reflection to convert an object into an intance of a base class. The stipulation is that the base class must have a public default constructor and there is no guaruntee that there will be no broken objects as a result of lost polymorphism. Also, the code must be run in a full trust context.
Visual Basic:
    Public Function DownCast(Of T As New)(ByVal source As Object) As T
        'A System.Type that represents the base type we are converting to
        'which we can use for reflection.
        Dim targetType As Type = GetType(T)
 
        'Throw an error if the source object does not derive from the target type.
        If Not source.GetType.IsSubclassOf(GetType(T)) Then
            Throw New ArgumentException("Source must be instance of a class derived from <T>.")
        End If
 
        'This is the resulting object, which is an instance of the base class.
        Dim Result As New T
 
        'Now we will loop through each field in the BASE CLASS and copy the value from
        'the source object to the resulting object.
        '(We need full trust because we must copy private members, which can't be accessed in partial trust contexts.)
        For Each field As FieldInfo In targetType.GetFields(_
            BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.FlattenHierarchy Or BindingFlags.Instance)
            field.SetValue(Result, field.GetValue(source))
        Next
 
        Return Result
    End Function
 
    Sub SillyExample()
        Dim Derived As New ArgumentException("message", "param name", New Exception())
        Dim Base As Exception = DownCast(Of Exception)(Derived)
  
        MessageBox.Show(Base.Message + " " + Base.InnerException.ToString())
    End Sub
 
Thanks. Not sure that I agree that in your example it would be wrong for OOP to allow an instance of ClassB to be converted to a ClassA. Yes, it would lose the ability to copy files from the Internet, but in making the conversion you must have decided that that's what you want anyway.

For me, this is only an issue for events which I'd like the inheriting class to stop raising. Clearly I can do this quite easily just by setting a flag, so it's not that big a problem, and I think I prefer to do this than use code like your example. But it would have been quite elegant to lose the events in a derived class by reducing the object to its base class.

My situation is that I am allowing the user to draw objects with the mouse, and once done, the objects are added to a collection for storage. The objects which have been drawn and the objects which are being drawn are both drawn in the same way, but the former needs to use the mousemove event to check if the cursor is over them, while the latter needs to use this same event to redraw the object. It would have been quite neat if I could have just had the drawing object inherit from the drawn object, overriding its mousemove event, and then downgrading it when drawing was completed.

Come to think of it, adding and removing handlers might be the best way to deal with this:

Visual Basic:
Public Class Form1

    Dim j2 As class2

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        j2 = New class2(Me)

    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        j2.SwitchHandler()
    End Sub

End Class

Public Class class2
    Private WithEvents f As Form1

    Sub New(ByVal ff As Form1)
        f = ff
        AddHandler f.MouseMove, AddressOf f_MouseMove
    End Sub

    Friend Sub SwitchHandler()
        RemoveHandler f.MouseMove, AddressOf f_MouseMove
        AddHandler f.MouseMove, AddressOf f_MouseMove2
    End Sub

    Private Sub f_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
        Beep()

    End Sub
    Private Sub f_MouseMove2(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
        f.Text = e.X
    End Sub
End Class

Methods and properties are not really an issue for me because you can just have a base class reference to your derived class. The methods and properties of the derived class are then effectively forgotten. It's just events.
 
Thanks. Not sure that I agree that in your example it would be wrong for OOP to allow an instance of ClassB to be converted to a ClassA. Yes, it would lose the ability to copy files from the Internet, but in making the conversion you must have decided that that's what you want anyway.

I tend to agree with marble_eater on this one, it sounds like you could do with a rethink of the class / inheritance structure.

In your case moving the drawn / drawing logic into a single base class that all drawing objects inherit from and using something like a flag may be a simpler method than switching the runtime implementation.
 
OK, here's a related but different situation. Supposing your object model has a Meal baseclass with a MeatType property. It's inherited by two subclasses, Rice and Noodles. You've got an order for Rice with Pork. Then the customer changes his mind and says he wants noodles instead of rice. You want to change the order object from a Rice one to a Noodles one without losing the Pork property. That seems a realistic possibility to me. So here's an adaptation of marble_eater's code to do that. I tried, but failed, to find a test I could carry out that would save me from having to catch errors for each property that didn't match. If you have any ideas how to do that then I'd be glad to hear them.

Visual Basic:
Imports System.Reflection

Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Dim j As New AClass

        j.BaseProperty = 12
        j.AProp = "ioh"
        j.DuplicatedProp = 123

        Dim u As BClass
        u = CrossCast(Of BClass)(j)

        Text = u.BaseProperty   'text shows 12
        MsgBox(u.DuplicatedProp)    'shows 0, as I would want.

    End Sub

    Friend Function CrossCast(Of T As New)(ByVal source As Object) As T
        'A System.Type that represents the base type we are converting to
        'which we can use for reflection.
        Dim targetType As Type = GetType(T)

        'Don't think there's any need to throw errors if they don't inherit from a common base. They will inherit from object in any case.

        'This is the resulting object, which is an instance of the base class.
        Dim Result As New T

        'Now we will loop through each field in the BASE CLASS and copy the value from
        'the source object to the resulting object.
        '(We need full trust because we must copy private members, which can't be accessed in partial trust contexts.)
        For Each field As FieldInfo In targetType.GetFields( _
            BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.FlattenHierarchy Or BindingFlags.Instance)
            Try
                field.SetValue(Result, field.GetValue(source))
            Catch
            End Try

        Next

        Return Result

    End Function
End Class

<Serializable()> Friend Class baseclass
    Friend BaseProperty As Long
End Class

<Serializable()> Friend Class AClass
    Inherits baseclass
    Friend AProp As String
    Friend DuplicatedProp As Long
End Class

<Serializable()> Friend Class BClass
    Inherits baseclass
    Friend BProp As Date
    Friend DuplicatedProp As Long
End Class
 
This sounds like a case where you're trying to force OO into a place it doesn't go. You might want to take a look at the Decorator Pattern.

No, I really don't want that sort of structure. For one thing, I'm displaying the objects in a propertygrid. If AClass and BClass hold references to an instance of baseclass instead of inheriting from it then I have to design a class inheriting from ExpandableObjectConverter to deal with the baseclass in the propertygrid. I neither want the hassle of doing that, nor the different appearance that it will give in the propertygrid.
 
This sounds like a case where you're trying to force OO into a place it doesn't go. You might want to take a look at the Decorator Pattern.


If you believe OO "doesn't go" here, try the following.

Add a menustrip to a form. Add a menu item. Through the Properties Window, set its margin on the left to 25 pixels (say). Right click on the menustrip to get the context menu. Select Convert To/TextBox. The ToolStripMenuItem object has now converted to a ToolStripTextBox and you will see that it has kept its left margin.

Now examine these controls in the Object Browser. The Margins property is a property of the Component class, from which both of these controls inherit, indirectly. They don't hold references to a Component object, they inherit from it. This sort of conversion is exactly what I want to do. I wonder how Microsoft goes about it?
 
This seems to work well:

Visual Basic:
   Friend Function CrossCast(Of T As New)(ByVal source As Object) As T

        'Loop to find the first shared parent type of T and source.
        Dim sharedparenttype As Type = GetType(T)
        Do Until sharedparenttype.BaseType Is Nothing Or source.GetType.IsSubclassOf(sharedparenttype)
            sharedparenttype = sharedparenttype.BaseType
        Loop

        'This is the resulting object, which is an instance of the base class.
        Dim Result As New T

        'Now we will loop through each field in the BASE CLASS and copy the value from
        'the source object to the resulting object.
        '(We need full trust because we must copy private members, which can't be accessed in partial trust contexts.)
        'If sharedparenttype is Object there will be no fields to loop through.
        For Each field As FieldInfo In sharedparenttype.GetFields( _
            BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.FlattenHierarchy Or BindingFlags.Instance)

            field.SetValue(Result, field.GetValue(source))

        Next

        Return Result

    End Function
 
In terms of converting between ToolStrip controls, all Microsoft does is create a new ToolStrip control and copy over properties (and probably events, too). There is no actual class-transmutation going on. Microsoft wrote code that deals with this specific conversion and copies specific properties, and exposes this code via verbs to the designer.

If you really want to incorporate this sort of "transmutational" functionality into you software in a generic manner, it would likely involve a base class for transmutable objects, lots of reflection to carry over data from one class to another, and lots of Attributes to identify how a property/field may behave during transmutation (for example, which properties are dispensible and can particular properties be converted to different types if necessary?).

As far as the property grid and the CLI are concerned, you can't expect to add such a fundamental modification of the way objects work and have pre-existing code and controls integrate neatly. An important aspect of all existing object-oriented languages and platforms is that at runtime, type-related data is immutable. This is critical for the sake of marshalling, allocating arrays and memory, compile time error checking and more. This is not to say that "transmutational types" wouldn't be a nifty thing to have on occasion, but rather that they aren't really worth the trouble. In the case of the property grid, this is wholly inconvenient for your sake, but in the end this is what works.
 
Yes, I appreciate that there's no actual conversion of one object to another going on here. But I'm trying to simulate it, as Microsoft does with toolstrip items. The only question is whether it's better to do it with Reflection or by working through every property that I have, line by line. I think the former.
 
It seems to me that the BindingFlags.FlattenHierarchy flag has no effect. Public fields of parent class types are returned by the GetFields method whether you have this flag or not. Can anyone shed any light on this? For my purposes I'd prefer not to have any inherited fields since I really want the private ones, and I can only get these by looping through the parent classes. Sample code below:

Visual Basic:
Imports System.Reflection

Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim j As New aclass
        Convert(GetType(bclass), j)

    End Sub

    Friend Sub Convert(ByVal t As Type, ByVal OldInstrument As Instrument)

        'Facility to simulate conversion of an instrument of one type into another.
        'Loop to find the first shared parent type of t and source.
        Dim sharedbaseclass As Type = t
        Do Until sharedbaseclass.BaseType Is Nothing Or OldInstrument.GetType.IsSubclassOf(sharedbaseclass)
            sharedbaseclass = sharedbaseclass.BaseType
        Loop

        'This is the new object, which is an instance of the base class.
        Dim NewInstrument As Instrument = Activator.CreateInstance(t)  'initialisation code, like setting parent child properties
        'should be in the New event. Setting default properties should not because I don't want it done here.

        'Loop through each field in the shared base class and copy the value from
        'the source object to the new object.
        'If sharedparenttype is t there will be no fields to loop through.

        For Each field As FieldInfo In sharedbaseclass.GetFields( _
            BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.FlattenHierarchy Or BindingFlags.Instance)

            field.SetValue(NewInstrument, field.GetValue(OldInstrument))
            MsgBox(field.Name)  'gives two messages - "PInstProp" and "BaseProp" whether BindingFlags.FlattenHierarchy is included or not.
        Next

    End Sub
End Class

Public Class cBase
    Private pName As String
    Public BaseProp As Date
End Class

Public Class Instrument
    Inherits cBase
    Private pInstProp As String
End Class

Public Class aclass
    Inherits Instrument
    Private intrate As Single
End Class

Public Class bclass
    Inherits Instrument
    Private denom As Long
End Class
 
BindingFlags.DeclaredOnly

BindingFlags.FlattenHierarchy only affects static (Shared) members. You need to use BindingFlags.DeclaredOnly to exclude inherited instance members.

Visual Basic:
sharedbaseclass.GetFields( _
    BindingFlags.Public Or _
    BindingFlags.NonPublic Or _
    BindingFlags.DeclaredOnly Or _
    BindingFlags.Instance _
)

Good luck :cool:
 
Back
Top