67

Downcasting means casting from a base class (or interface) to a subclass or leaf class.

An example of a downcast might be if you cast from System.Object to some other type.

Downcasting is unpopular, maybe a code smell: Object Oriented doctrine is to prefer, for example, defining and calling virtual or abstract methods instead of downcasting.

  • What, if any, are good and proper use cases for downcasting? That is, in what circumstance(s) is it appropriate to write code that downcasts?
  • If your answer is "none", then why is downcasting supported by the language?
  • 5
    What you describe is usually called "downcasting". Armed with that knowledge, could you do some more research of your own first, and explain why existing reasons that you found are not sufficient? TL;DR: downcasting breaks static typing, but sometimes a type system is too restrictive. So it's sensible for a language to offer safe downcasting. – amon Feb 26 '18 at 13:21
  • Do you mean downcasting? Upcasting would be up through the class hierarchy, i.e. from subclass to superclass, as opposed to downcasting, from superclass to subclass ... – Andrei Socaciu Feb 26 '18 at 13:22
  • My apologies: you're right. I edited the question to rename "upcast" to "downcast". – ChrisW Feb 26 '18 at 13:27
  • @amon en.wikipedia.org/wiki/Downcasting gives "convert to string" as an example (which .Net supports using a virtual ToString); its other example is "Java containers" because Java doesn't support generics (which C# does). stackoverflow.com/questions/1524197/downcast-and-upcast says what downcasting is, but without examples of when it's appropriate. – ChrisW Feb 26 '18 at 13:33
  • 4
    @ChrisW That's before Java had support for generics, not because Java doesn't support generics. And amon pretty much gave you the answer - when the type system you're working with is too restrictive and you're not in a position to change it. – Ordous Feb 26 '18 at 13:47

10 Answers 10

12

Here are some proper uses of downcasting.

And I respectfully vehemently disagree with others here who say the use of downcasts is definitely a code smell, because I believe there is no other reasonable way to solve the corresponding problems.

Equals:

class A
{
    // Nothing here, but if you're a C++ programmer who dislikes object.Equals(object),
    // pretend it's not there and we have abstract bool A.Equals(A) instead.
}
class B : A
{
    private int x;
    public override bool Equals(object other)
    {
        var casted = other as B;  // cautious downcast (dynamic_cast in C++)
        return casted != null && this.x == casted.x;
    }
}

Clone:

class A : ICloneable
{
    // Again, if you dislike ICloneable, that's not the point.
    // Just ignore that and pretend this method returns type A instead of object.
    public virtual object Clone()
    {
        return this.MemberwiseClone();  // some sane default behavior, whatever
    }
}
class B : A
{
    private int[] x;
    public override object Clone()
    {
        var copy = (B)base.Clone();  // known downcast (static_cast in C++)
        copy.x = (int[])this.x.Clone();  // oh hey, another downcast!!
        return copy;
    }
}

Stream.EndRead/Write:

class MyStream : Stream
{
    private class AsyncResult : IAsyncResult
    {
        // ...
    }
    public override int EndRead(IAsyncResult other)
    {
        return Blah((AsyncResult)other);  // another downcast (likely ~static_cast)
    }
}

If you're going to say these are code smells, you need to provide better solutions for them (even if they are avoided due to inconvenience). I don't believe better solutions exist.

  • 9
    "Code smell" does not mean "this design is definitely bad" it means "this design is suspicious". That there are language features that are inherently suspicious is a matter of pragmatism on the part of the language author – Caleth Feb 27 '18 at 12:45
  • 5
    @Caleth: And my entire point is that these designs come up all the time, and that there is absolutely nothing inherently suspicious about the designs I have shown, and hence the casting is not a code smell. – Mehrdad Feb 27 '18 at 12:54
  • 4
    @Caleth I disagree. Code smell, to me, means that the code is bad or, at the very least, could be improved. – Konrad Rudolph Feb 27 '18 at 13:03
  • 6
    @Mehrdad Your opinion seems to be that code smells have to be the fault of application programmers. Why? Not every code smell has to be an actual problem or have a solution. A code smell according to Martin Fowler is simply "a surface indication that usually corresponds to a deeper problem in the system" object.Equals fits that description quite well (try to correctly provide an equals contract in a superclass that allows subclasses to correctly implement it - it's absolutely non-trivial). That as application programmers we can't solve it (in some cases we can avoid it) is a different topic. – Voo Feb 27 '18 at 21:16
  • 5
    @Mehrdad Well let's see what one of the original language designers has to say about Equals.. Object.Equals is horrid. Equality computation in C# is completely messed up (Eric's comment on his answer). It's funny how you don't want to reconsider your opinion even when one of the lead designers of the language itself tells you you're wrong. – Voo Feb 28 '18 at 8:31
135

Downcasting is unpopular, maybe a code smell

I disagree. Downcasting is extremely popular; a huge number of real-world programs contain one or more downcasts. And it is not maybe a code smell. It is definitely a code smell. That's why the downcasting operation is required to be manifest in the text of the program. It's so that you can more easily notice the smell and spend code review attention on it.

in what circumstance[s] is it appropriate to write code which downcasts?

In any circumstance where:

  • you have a 100% correct knowledge of a fact about the runtime type of an expression that is more specific than the compile-time type of the expression, and
  • you need to take advantage of that fact in order to use a capability of the object not available on the compile-time type, and
  • it is a better use of time and effort to write the cast than it is to refactor the program to eliminate either of the first two points.

If you can cheaply refactor a program so that either the runtime type can be deduced by the compiler, or to refactor the program so that you don't need the capability of the more derived type, then do so. Downcasts were added to the language for those circumstances where it is hard and expensive to thus refactor the program.

why is downcasting supported by the language?

C# was invented by pragmatic programmers who have jobs to do, for pragmatic programmers who have jobs to do. The C# designers are not OO purists. And the C# type system is not perfect; by design it underestimates the restrictions that can be placed on the runtime type of a variable.

Also, downcasting is very safe in C#. We have a strong guarantee that the downcast will be verified at runtime and if it cannot be verified then the program does the right thing and crashes. This is wonderful; it means that if your 100% correct understanding of type semantics turns out to be 99.99% correct, then your program works 99.99% of the time and crashes the rest of the time, instead of behaving unpredictably and corrupting user data 0.01% of the time.


EXERCISE: There is at least one way to produce a downcast in C# without using an explicit cast operator. Can you think of any such scenarios? Since these are also potential code smells, what design factors do you think went into the design of a feature that could produce a downcast crash without having a manifest cast in the code?

  • 101
    Remember those posters in high school on the walls "What is popular is not always right, what is right is not always popular?" You thought they were trying to keep you from doing drugs or getting pregnant in high school but they were actually warnings about the dangers of technical debt. – corsiKa Feb 26 '18 at 16:01
  • 14
    @EricLippert, the foreach statement, of course. This was done before IEnumerable was generic, right? – Arturo Torres Sánchez Feb 26 '18 at 17:18
  • 18
    This explanation made my day. As a teacher, I often get asked why C# is designed in this or that way, and my answers are often based on motivations you and your colleagues have shared here and on your blogs. It's often a bit harder to answer the follow-up question "But whyyyyy?!". And here I find the perfect follow-up answer: "C# was invented by pragmatic programmers who have jobs to do, for pragmatic programmers who have jobs to do.". :-) Thank you @EricLippert! – Zano Feb 26 '18 at 22:39
  • 12
    Aside: Obviously in my comment above I meant to say we have a downcast from A to B. I really dislike the use of "up" and "down" and "parent" and "child" when navigating a class hierarchy and I get them wrong all the time in my writing. The relationship between the types is a superset/subset relationship, so why there should be a direction up or down associated with it, I don't know. And don't even get me started on "parent". The parent of Giraffe is not Mammal, the parent of Giraffe are Mr. and Mrs. Giraffe. – Eric Lippert Feb 26 '18 at 23:12
  • 9
    Object.Equals is horrid. Equality computation in C# is completely messed up, far too messed up to explain in a comment. There are so many ways to represent equality; it is very easy to make them inconsistent; it is very easy, in fact required to violate the properties required of an equality operator (symmetry, reflexivity and transitivity). Were I designing a type system from scratch today there would be no Equals (or GetHashcode or ToString) on Object at all. – Eric Lippert Feb 27 '18 at 14:34
33

Event handlers usually have the signature MethodName(object sender, EventArgs e). In some cases it's possible to handle the event without regard for what type sender is, or even without using sender at all, but in others sender must be cast to a more-specific type to handle the event.

For example, you may have two TextBoxs and you want to use a single delegate to handle an event from each of them. Then you would need to cast sender to a TextBox so that you could access the necessary properties to handle the event:

private void TextBox_TextChanged(object sender, EventArgs e)
{
   TextBox box = (TextBox)sender;
   box.BackColor = string.IsNullOrEmpty(box.Text) ? Color.Red : Color.White;
}

Yes, in this example, you could create your own subclass of TextBox which did this internally. Not always practical or possible, though.

  • 2
    Thank you, that is a good example. The TextChanged event is a member of Control -- I wonder why sender isn't of type Control instead of type object? Also I wonder whether (theoretically) the framework could have redefined the event for each subclass (so that e.g. TextBox could have a new version of the event, using TextBox as the type of sender) ... that might (or might not) require downcasting (to TextBox) inside the TextBox implementation (to implement the new event type), but would avoid requiring a downcast in the application code's event handler. – ChrisW Feb 26 '18 at 14:48
  • 3
    This is considered acceptable because it is difficult to generalize event handling if every event has its own method signature. Though I suppose it's an accepted limitation rather than something that's considered best practice because the alternative is actually more troublesome. If it were possible to start off with a TextBox sender rather than an object sender without overly complicating the code, I'm sure it would be done. – Neil Feb 26 '18 at 15:52
  • 6
    @Neil The eventing systems are specifically made to allow forwarding arbitrary data chunks to arbitrary objects. That's nigh impossible to do without runtime-checks for type correctness. – Joker_vD Feb 26 '18 at 20:19
  • @Joker_vD Ideally the API would of course support strongly typed callback signatures using generic contravariance. The fact that .NET (overwhelmingly) doesn’t do this is merely due to the fact that generics and covariance were introduced later. — In other words, downcasting here (and, generally, elsewhere) is required due to inadequacies in the language/API, not because it’s fundamentally a good idea. – Konrad Rudolph Feb 27 '18 at 10:17
  • @KonradRudolph Sure, but how would you store events of arbitrary types in the event queue? Do you just get rid of queuing and go for direct dispatch? – Joker_vD Feb 27 '18 at 10:22
17

You need downcasting when something gives you a supertype and you need to handle it differently depending on the subtype.

decimal value;
Donation d = user.getDonation();
if (d is CashDonation) {
     value = ((CashDonation)d).getValue();
}
if (d is ItemDonation) {
     value = itemValueEstimator.estimateValueOf(((ItemDonation)d).getItem());
}
System.out.println("Thank you for your generous donation of $" + value);

No, this does not smell good. In a perfect world Donation would have an abstract method getValue and each implementing subtype would have an appropriate implementation.

But what if these classes are from a library you can not or don't want to change? Then there is no other option.

  • This looks like a good time to use the visitor pattern. Or the dynamic keyword which achieves the same result. – user2023861 Feb 26 '18 at 19:57
  • 3
    @user2023861 No I think the visitor pattern depends on cooperation from the Donation subclasses -- i.e. Donation needs to declare an abstract void accept(IDonationVisitor visitor), which its subclasses implement by calling specific (e.g. overloaded) methods of IDonationVisitor. – ChrisW Feb 26 '18 at 20:14
  • @ChrisW, you're right about that. Without cooperation, you could still do it using the dynamic keyword though. – user2023861 Feb 26 '18 at 20:41
  • In this particular case you could add an estimateValue method to Donation. – immibis Feb 27 '18 at 21:33
  • @user2023861: dynamic still downcasting, just slower. – Mehrdad Feb 28 '18 at 18:53
12

To add to Eric Lippert's answer since I can't comment...

You can often avoid downcasting by refactoring an API to use generics. But generics weren't added to the language until version 2.0. So even if your own code doesn't need to support ancient language versions, you may find yourself using legacy classes that aren't parameterized. For example, some class may be defined to carry a payload of type Object, which you're forced to cast to the type you know it to actually be.

  • Indeed, one might say that generics in Java or C# are essentially just a safe wrapper around storing superclass objects and downcasting to the correct (compiler-checked) subclass before using the elements again. (Unlike C++ templates, which rewrite the code to always use the subclass itself and thus avoid having to downcast – which can boost memory performance, if often at the expense of compilation time and executable size.) – leftaroundabout Feb 27 '18 at 9:39
4

There is a trade-off between static and dynamically typed languages. Static typing gives the compiler a lot of information to be able to make rather strong guarantees about the safety of (parts of) programs. This comes at a cost, however, because not only do you need to know that your program is correct, but you must write the appropriate code to convince the compiler that this is the case. In other words, making claims is easier than proving them.

There are "unsafe" constructs that help you make unproven assertions to the compiler. For example, unconditional calls to Nullable.Value, unconditional downcasts, dynamic objects, etc. They allow you to assert a claim ("I assert that object a is a String, if I'm wrong, throw a InvalidCastException") without needing to prove it. This can be useful in cases where proving it is significantly harder than worthwhile.

Abusing this is risky, which is exactly why the explicit downcast notation exists and is mandatory; it's syntactic salt meant to draw attention to an unsafe operation. The language could have been implemented with implicit downcasting (where the inferred type is unambiguous), but that would hide this unsafe operation, which is not desirable.

  • I guess I'm asking, then, for some example[s] of where/when it's necessary or desirable (i.e. good practice) to make this kind of "unproven assertion". – ChrisW Feb 26 '18 at 17:39
  • 1
    How about casting the result of a reflection operation? stackoverflow.com/a/3255716/3141234 – Alexander Feb 26 '18 at 19:48
3

Downcasting is considered bad for a couple of reasons. Primarily I think because it's anti-OOP.

OOP would really like it if you never ever had to downcast as its ''raison-d'etre'' is that polymorphism means you don't have to go

if(object is X)
{
    //do the thing we do with X's
}
else
{
    //of the thing we do with Y's
}

you just do

x.DoThing()

and the code auto-magically does the right thing.

There are some 'hard' reasons not to downcast:

  • In C++ it is slow.
  • You get a runtime error if you choose the wrong type.

But the alternative to downcasting in some scenarios can be pretty ugly.

The classic example is message processing, where you don't want to add the processing function to the object, but keep it in the message processor. I then have a tonne of MessageBaseClass in an array to process, but I also need each one by subtype for correct processing.

You can use Double Dispatch to get around the problem (https://en.wikipedia.org/wiki/Double_dispatch)... But that also has its issues. Or you can just downcast for some simple code at the risk of those hard reasons.

But this was before Generics were invented. Now you can avoid downcasting by providing a type where the details specified later.

MessageProccesor<T> can vary its method parameters and return values by the specified type but still provide generic functionality

Of course no-one is forcing you to write OOP code and there are plenty of language features that are provided but frowned upon, such as reflection.

  • 1
    I doubt it's slow in C++, especially if you static_cast instead of dynamic_cast. – ChrisW Feb 26 '18 at 14:38
  • "Message-processing" -- I think of that as meaning e.g. casting lParam to something specific. I'm not sure that's a good C# example, though. – ChrisW Feb 26 '18 at 15:03
  • 3
    @ChrisW - well, yes, static_cast is always fast ... but isn't guaranteed not to fail in undefined ways if you make a mistake and the type isn't what you're expecting. – Jules Feb 26 '18 at 16:31
  • @Jules: That's completely beside the point. – Mehrdad Feb 28 '18 at 10:28
  • @Mehrdad, it exactly the point. to do the equivalent c# cast in c++ is slow. and this is the traditional reason against casting. the alternate static_cast is not equivalent and considered the worse choice due to the undefined behaviour – Ewan Feb 28 '18 at 10:47
0

In addition to all before said, imagine a Tag property that is of type object. It provides a way for you to store an object of your choice in another object for later use as you need. You need downcast this property.

In general, not using something until today is not always an indication of something useless ;-)

  • Yes see for example What use is the Tag property in .net. In one of the answers there, someone wrote, I used to use it to input instructions to the user in Windows Forms applications. When the control GotFocus event triggered, the instructions Label.Text property was assigned the value of my control Tag property which contained the instruction string. I guess an alternative way to 'extend' the Control (instead of using object Tag property) would be to create a Dictionary<Control, string> in which to store the label for each control. – ChrisW Feb 26 '18 at 16:57
  • 1
    I like this answer because Tag is a public property of a .Net framework class, which shows that you're (more-or-less) supposed to use it. – ChrisW Feb 26 '18 at 17:24
  • @ChrisW: Not to say the conclusion is wrong (or right), but note that that's sloppy reasoning, because there is plenty of public .NET functionality that is obsolete (say, System.Collections.ArrayList) or otherwise deprecated (say, IEnumerator.Reset). – Mehrdad Feb 28 '18 at 10:37
0

The most common use of downcasting in my work is due to some idiot breaking Liskov’s Substitution principle.

Imagine there’s an interface in some third party lib.

public interface Foo
{
     void SayHello();
     void SayGoodbye();
 }

And 2 classes that implement it. Bar and Qux. Bar is well behaved.

public class Bar
{
    void SayHello()
    {
        Console.WriteLine(“Bar says hello.”);
    }
    void SayGoodbye()
    {
         Console.WriteLine(“Bar says goodbye”);
    }
}

But Qux isn’t well behaved.

public class Qux
{
    void SayHello()
    {
        Console.WriteLine(“Qux says hello.”);
    }
    void SayGoodbye()
    {
        throw new NotImplementedException();
    }
}

Well... now I don’t have a choice. I have to type check and (possibly) downcast in order to avoid having my program come to a crashing halt.

  • 1
    Not true for this particular example. You could just catch the NotImplementedException when calling SayGoodbye. – Per von Zweigbergk Feb 28 '18 at 7:17
  • It’s an overly simplified example maybe, but work with some of the older .Net APIs a bit and you’ll run into this situation. Sometimes the easiest way to write the code is to safe cast ala var qux = foo as Qux;. – RubberDuck Feb 28 '18 at 11:32
0

Another real-world example is WPF's VisualTreeHelper. It uses downcast to cast DependencyObject to Visual and Visual3D. For example, VisualTreeHelper.GetParent. The downcast happens in VisualTreeUtils.AsVisualHelper:

private static bool AsVisualHelper(DependencyObject element, out Visual visual, out Visual3D visual3D)
{
    Visual elementAsVisual = element as Visual;

    if (elementAsVisual != null)
    {
        visual = elementAsVisual;
        visual3D = null;
        return true;
    }

    Visual3D elementAsVisual3D = element as Visual3D;

    if (elementAsVisual3D != null)
    {
        visual = null;
        visual3D = elementAsVisual3D;
        return true;
    }            

    visual = null;
    visual3D = null;
    return false;
}

protected by gnat Mar 29 at 17:44

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.