views:

267

answers:

4

What is your perspective on downcasting? Is it ALWAYS wrong, or are there cases where it is acceptable, or even preferable or desired?

Is there some good measure/guideline we can give that tells us when downcasting is "evil", and when it's "ok"/"good"?

(I know a similar question exists, but that question spins out from a concrete case. I'd like to have it answered from a general design perspective.)

+6  A: 

No, it's definitely not always wrong.

For example, suppose in C# you have an event handler - that gets given a sender parameter, representing the originator of the event. Now you might hook up that event handler to several buttons, but you know they're always buttons. It's reasonable to cast sender to Button within that code.

That's just one example - there are plenty of others. Sometimes it's just a way around a slightly awkward API, other times it comes out of not being able to express the type within the normal type system cleanly. For example, you might have a Dictionary<Type, object> appropriate encapsulated, with generic methods to add and retrieve values - where the value of an entry is of the type of the key. A cast is entirely natural here - you can see that it will always work, and it's giving more type safety to the rest of the system.

Jon Skeet
True, Jon, but couldn't the case of C# events be (at least in part) avoided with the introduction of co-/contra-variant types? You would be able to hook up to e.g. a (Button, ButtonEventArgs) handler if you were hooking to a button, and an (object, EventArgs) if you were in a context where you were hooking to a base type...
Håvard S
@Havard S: That's possible, but may not always be practical... and don't forget that we often have to live with the API we're given. I'm certainly not going to rewrite the whole of WinForms just to avoid an occasional cast...
Jon Skeet
Of course, given legacy, downcasting is practical, but without legacy you'd be able to avoid it for the event case given a sufficient type system.
Håvard S
@Havard S: I'm not even sure about that, in fact... at least, not as simply as it sounds. Okay, so Button might expose an event which sent Button as a sender. What about FancyButton (derived from Button). Does it have a *new* Click event which sends FancyButton as a sender? Even with generics, this becomes pretty ugly.
Jon Skeet
Well, the basic resolution would be to have covariant events, i.e. FancyButton would send a FancyButton as sender. The hard part comes when defining how polymorphism works (how does overloading work with respect to argument types), and/or how you build on the other arguments of the handler (the args), for which there is no real useful definition of covariance.You're right, it'll get ugly. :)
Håvard S
+1  A: 

It's never an ideal solution and should be avoided wherever possible - unless the alternative would be worse. Sometimes, it cannot be avoided, e.g. pre-Generics Java's Standard API library had lots of classes (most prominently the collections) that required downcasting to be useful. And sometimes, changing the design to avoid the downcast would complicate it significantly, so that the downcast is the better solution.

Michael Borgwardt
+1  A: 

An example for "legal" downcasting is Java pre 5.0 where you had to downcast container elements to their concrete type when accessing them. It was unavoidable in that context. This also shows the other side of the question though: if you need to downcast a lot in a given situation, it starts to be evil, so it is better to find another solution without downcasting. That resulted in the introduction of generics in Java 5.

John Vlissides analyzes this issue (aka "Type Laundering") a lot in his excellent book Pattern Hatching (practically a sequel to Design Patterns).

Péter Török
Good book reference, thanks. That's another aspect of the question, really: Is it possible to give a general measure for when downcasting is / "starts to become" evil?
Håvard S
@Håvard I am afraid there is no objective measure for this. Jon gives nice examples, which IMO could be summed up like this rule of thumb: if the downcasts are localized to a small portion of your code and you can be sure that they always succeed, then they are OK. If downcasts are spread over the code and/or there is a chance of casting failures, then they are bad.
Péter Török
Well, hence the call for some least "common consent". Good summary.
Håvard S
A: 

2 words: visitor pattern.

vzor