Polymorphism and Operator Overloading don't mix !

Posted Fri, Jan 7 2005 12:51 by bill

Just like drinking and driving, seperately both are fun things, but put them together and we have a situation where loss becomes a likely result

Consider this code :

Public Class Invoice

   Public Price As Decimal

 

   Shared Operator +(ByVal first As Invoice, ByVal second As Invoice) As Invoice

   Dim retVal As New Invoice

      retVal.Price = first.Price + second.Price

      Return retVal

   End Operator

 

   Public Overrides Function ToString() As String

      Return "Invoice Price = " & Me.Price

   End Function

 

End Class

 

 

Public Class ShippingInvoice

   Inherits Invoice

 

   Public ShiipingCost As Decimal

 

   Shared Shadows Operator +(ByVal first As ShippingInvoice, ByVal second As ShippingInvoice) As ShippingInvoice

   Dim retVal As New ShippingInvoice

      With retVal

         .Price = first.Price + second.Price

         .ShiipingCost = first.ShiipingCost + second.ShiipingCost

      End With

      Return retVal

   End Operator

 

   Public Overrides Function ToString() As String

      Return "ShippingInvoice Price = " & Me.Price & ", ShippingCost = " & Me.ShiipingCost

   End Function

 

End Class

 

Now if you then had a method such as :

 

   Sub PolyAndOpsDontMix(ByVal a As Invoice, ByVal b As Invoice)

      Console.WriteLine((a + b).ToString)

   End Sub

 

And you call that, passing into the mehtod ShippingInvoices, the console output will be of type Invoice, not ShippingInvoice, and hence all the ShippingCost information is lost.  The operator is based on the type of the operands at compile time, not runtime.  So be careful when using Operator Overlaoding, especially when the operands are passed into a method, and hence polymorphism should apply. If in doubt seal those classes (NotInheritable) or use a method call, rather than an operator. Remeber operator overlaoding can be fun, and so too can polymorphism, but mixing the two can result in data loss.

 

 

Filed under: ,

Comments

# re: Polymorphism and Operator Overloading don't mix !

Friday, January 07, 2005 1:06 PM by bill

What if you made Operator+ overridable in Invoice and Overrides in ShippingInvoice?

# re: Polymorphism and Operator Overloading don't mix !

Friday, January 07, 2005 1:13 PM by bill

# Re: Polymorphism and Operator Overloading don't mix !

Friday, January 07, 2005 1:57 PM by bill

Hi Matthew,

Operators are Shared, so you can Overload them or hide them with Shadows, but you cannot make them virtual which is what Overrides requires. It's for this very reason there connot be polymorphism with operators as they are Shared (aka static)

# re: Polymorphism and Operator Overloading don't mix !

Saturday, January 08, 2005 1:12 AM by bill

"ShiipingCost?"

You could probably accomplish what you want by providing an overridable instance method (ie, Invoice Invoice.AddTo(Invoice a)), then calling that from the operator in Invoice. Derived classes would redefine that method rather than shadow the operator.

# re: Polymorphism and Operator Overloading don't mix !

Saturday, January 08, 2005 6:41 AM by bill

You do realize that this is the same in any other method in .Net as well? And in any other static typing OO language that I know.
What you've here is a simple compile time checking, nothing more.

# Re: Polymorphism and Operator Overloading don't mix !

Saturday, January 08, 2005 11:08 AM by bill

hi Dan,

Exactly !! If they want polymorphic behaviour then they need to use instance methods that are Overridable. One problem though that you do get with Overridable methods is that the type is that the derived type cannot be represented (strongly typed), for example, let's say we had an Add method :

Public Overridable Function Add(value As Invoice) As Invoice

When we go to override that in the derived class, the return type remains as Invoice, not ShippingInvoice. SO what happens is as we extend our hierarchy, we are forced to extend the number of methods. ShippingInvoice becomes:

Public Overrides Function Add(value As Invoice) As Invoice

Public Overridable Function Add(value As ShippingInvoice) As ShippingInvoice



BTW: looks like I need a spell checker on my property/public field declarations

Bill

# Re: Polymorphism and Operator Overloading don't mix !

Saturday, January 08, 2005 11:14 AM by bill

Hi Ayende,

This is NOT the same in any other method in .NET, at least not instance methods. What we are looking at here is the issues that arise from operators being shared (aka static), something which may not be obvious from the code that uses the operators but should be obvious from the code that declares the operator. But if these calls were instance methods then the issue would not arise.

Bill.

# Blog link of the week 01

Sunday, January 09, 2005 4:10 PM by TrackBack

Blog link of the week 01

# re: Polymorphism and Operator Overloading don't mix !

Thursday, February 03, 2005 7:28 AM by bill

Ok, this is a rather tricky scenario! But I think Dan's idea, taken internally as a Protected Overridable Method, called by the +Operator, and then overridden within the Derived Class, should be the ticket:

Public Class Invoice
Public Price As Decimal

Shared Operator +(ByVal first As Invoice, ByVal second As Invoice) As Invoice
Return first.Add(second)
End Operator

Protected Overridable Function Add(ByVal addInvoice As Invoice) As Invoice
Dim retVal As New Invoice
retVal.Price = Me.Price + addInvoice.Price
Return retVal
End Function

Public Overrides Function ToString() As String
Return "Invoice Price = " & Me.Price
End Function
End Class


Public Class ShippingInvoice
Inherits Invoice

Public ShippingCost As Decimal

Shared Shadows Operator +(ByVal first As ShippingInvoice, _
ByVal second As ShippingInvoice) As ShippingInvoice
Return DirectCast(first.Add(second), ShippingInvoice)
End Operator
Shared Shadows Operator +(ByVal first As Invoice, _
ByVal second As ShippingInvoice) As Invoice
Return second.Add(first)
End Operator
Shared Shadows Operator +(ByVal first As ShippingInvoice, _
ByVal second As Invoice) As Invoice
Return first.Add(second)
End Operator

Protected Overrides Function Add(ByVal addInvoice As Invoice) As Invoice
' Overrides Invoice.Add() which is the Protected, internal implementation
' for the '+' binary operator.

If TypeOf addInvoice Is ShippingInvoice Then
With DirectCast(addInvoice, ShippingInvoice)
Dim retVal As New ShippingInvoice
retVal.Price = Me.Price + .Price
retVal.ShippingCost = Me.ShippingCost + .ShippingCost
Return retVal
End With
Else
Return MyBase.Add(addInvoice)
End If
End Function

Public Overrides Function ToString() As String
Return "ShippingInvoice Price = " & Me.Price & _
", ShippingCost = " & Me.ShippingCost
End Function
End Class

The above seems to do the trick. It's not necessarily "perfect" in that I really was not sure how to handle mixed-class operations and had to make a choice. For example, how should one handle BaseClass + DerivedClass? Should one add them as BaseClass's, or just Throw an Exception on the basis that this has "no valid meaning"?

I decided to take a "lowest common denominator" approach and decided to make it legal, but to have the results be interpreted as a sort of "BaseClass.Add()" methodology. The other approach would be to have it take a "DerivedClass.Add()" methodology, but consider the BaseClass.ShippingCost = 0. Or maybe just Trow an Exception in a Mixed situation.

However, adopting a "DerivedClass.Add()" methodology cannot work in the above as-is since the BaseClass cannot have a clue what to do. It can't even know to Throw an Exception in a mixed-class scenario, because it can't even know that there is one. Or can it..?

I'll follow-up with an explanation of how to pull that off if anyone is interested...

Mike

# re: Polymorphism and Operator Overloading don't mix !

Wednesday, June 01, 2005 1:53 AM by bill

Sorry for asking a dumb question, but what does DirectCast do?

# Desktop Drivers

Friday, February 28, 2014 12:34 AM by Desktop Drivers Update

Desktop Drivers Utility can help you update the old drivers with the latest drivers for your computer and increase the ease at which you can run the latest CPU-intensive software such as games.