78

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?
ChrisW
  • 3,417
  • 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 https://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). https://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
  • @Ordous I don't understand that answer: could you give some example[s]? – ChrisW Feb 26 '18 at 13:49
  • @ChrisW, The "string" example in Wikipedia is very weak. It basically just shows a round about way of converting a reference to a String into a reference to a String. Not much point in it. A better example would be a function that operates in some generic way on an Animal, but then does something special if the Animal happens to be a Dog. – Solomon Slow Feb 26 '18 at 13:59
  • @jameslarge If you know any references which give good examples of downcasting, I'd like to know. I looked at the first few pages of https://www.google.fr/search?q=downcasting+c%23 and it only returned trivial examples, using Shape and so on, to explain what it is but not when to use it. – ChrisW Feb 26 '18 at 14:08
  • @ChrisW, The time to use downcasting is when you need to work around a defect in a broken design because you have limited time in which to solve some problem that the design failed to anticipate. – Solomon Slow Feb 26 '18 at 15:21
  • Whenever you use findViewById() in Android you usually have to cast the result. – AndreKR Feb 27 '18 at 09:40
  • It's a fundamental part of polymorphism. You should try to use the most specific interface, abstract class, or superclass possible, but there are definitely real-world cases for objects.

    MVC has ViewData and ViewBag ASP.NET (and by extension MVC / Web.API) can store reference types in Session, which are pulled out as objects.

    Use the is / as constructs to check and cast the type. Use a generic if it works for your scenario.

    Another example: for example, a List can only contain T. It cannot contain T1 and T2. You could make List or just use an ArrayList for a mixed set.

    – Julia McGuigan Feb 28 '18 at 03:15
  • it smells like homework – Alessandro Teruzzi Feb 28 '18 at 17:04
  • Perfect example of legitimate downcasting: When needing to adjust or add data annotations on a parent class and the class you are working working with is a member of another class. Not legal in C#, but Swift provides "as!" and "as?" operators for just such exceptions.. – Thomas Phaneuf Mar 26 '21 at 12:28

10 Answers10

149

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?

Eric Lippert
  • 46,189
  • The most common use case for downcasting is when we you are dealing with a property change callback in your DependencyProperty. This is really important if you have any custom controls. – Berin Loritsch Feb 26 '18 at 15:21
  • 2
    Thank you for answering. "extremely popular" funny :-) Are there any good examples I should know of? "manifest in the text of the program" I don't know: it's just an identifier in parentheses, not easy to Find in Files (unlike C++, which evolved to using even-more-evident xxx_cast<>() instead). "Can you think of any such scenarios?" Using the dynamic keyword? – ChrisW Feb 26 '18 at 15:28
  • 1
    @ChrisW: Yep, dynamic is one. dynamic is essentially object where a cast is inserted for you at runtime that casts to the runtime type, which is safe. There are also ways in C# to introduce downcasts that can fail at runtime; see if you can think of any. – Eric Lippert Feb 26 '18 at 15:38
  • 106
    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
  • ... now I'm stuck wondering about those other ways. I know some in Java (e.g. the combination of generics and vararg methods), but my understanding is that C# managed to avoid that particular issue. Unfortunately, I've not spent enough time working in C# to know it in enough depth to figure any out... – Jules Feb 26 '18 at 16:25
  • 15
    @EricLippert, the foreach statement, of course. This was done before IEnumerable was generic, right? – Arturo Torres Sánchez Feb 26 '18 at 17:18
  • 5
    @ArturoTorresSánchez: Correct! foreach introduces several casts, the most interesting of which is the cast to the loop variable type. And yes, it was put in there before the days of generics. Had generics been in the type system from the beginning, probably the foreach loop would not have inserted a cast. – Eric Lippert Feb 26 '18 at 17:20
  • 1
    The compound assignment operators += and so on introduce casts but they are representation changing casts and not casts up and down a type hierarchy. For example, the compound assignment in byte b; ... b += 1; is processed as b = (byte)((int)b+1); but that is not an example of a downcast in the sense of this question because byte and int do not have a supertype/subtype relationship. – Eric Lippert Feb 26 '18 at 17:28
  • There is an undocumented scenario where the C# compiler introduces a downcast, involving interop. Anyone know what it is? (Hint: is it ever possible for B b = new B(); to throw an invalid conversion exception on the assignment to b?) – Eric Lippert Feb 26 '18 at 17:28
  • There is also a scenario where you can put a cast operator in a program and a downcast happens but not to the type in the cast operator. What is it? – Eric Lippert Feb 26 '18 at 17:34
  • @EricLippert With numeric types, will short x = 1; long y = (int)x; generate a cast from short to long without a cast to int? – Andrew Feb 26 '18 at 20:27
  • @EricLippert, or is it because of auto-boxing? – NH. Feb 26 '18 at 20:40
  • 1
    @EricLippert I know from using Excel interop that you can "new" an interop interface. So you can say B b = new B(); even though B is an interface. Underneath this is really creating (I think) a .NET wrapper class around (I think) a COM class. But... that's not really a downcast, it's more of an upcast, so not sure how close I am to guessing. – Dave Cousineau Feb 26 '18 at 20:42
  • (Maybe the downcast is from the COM type to the .NET type? Which could throw if you didn't implement the interop type properly? I probably don't know enough to guess correctly.) – Dave Cousineau Feb 26 '18 at 20:49
  • 1
    (Reading, it sounds like the cast is from System.__ComObject to the interop interface. This can fail if the COM object does not provide enough information/is not configured properly for this kind of usage.) – Dave Cousineau Feb 26 '18 at 20:54
  • 8
    @DaveCousineau: Correct, doing new on an interop interface gives you back an instance of the coclass, and it query-interfaces for the interface, which can fail at runtime. You're right that it's a kind of half-upcast, half-downcast. Either way, it is super weird that new B() can result in an error when attempting to convert to B. – Eric Lippert Feb 26 '18 at 21:14
  • @AndrewPiliser: The optimizer would be within its rights to do so, since there is no possible visible difference. Also, shorts need not be stored in a memory location or register of short size; it might already be integer or long sized when the conversion to long happens. – Eric Lippert Feb 26 '18 at 21:16
  • @EricLippert "There is also a scenario where you can put a cast operator in a program and a downcast happens but not to the type in the cast operator. What is it?" Unboxing a value type? object o = 3; int i = (int)o; Feeling a tad iffy on my internal reasoning. – Jesse C. Slicer Feb 26 '18 at 21:40
  • 4
    @JesseC.Slicer: Nope. Suppose we have class A {} class B : A {} class C{ public static explicit operator C(B m) { return new D(); } } class D : C { } and then A a = new B(); D d = (D)a;. The compiler generates the code as (D)(C)(B)a, so we have an upcast from A to B not manifest in the source code text, and then an upcast from C to D that is in the source code. – Eric Lippert Feb 26 '18 at 22:01
  • 2
    Note that the compiler never chains user defined conversions like this but it can put a built-in conversion on either side of a user-defined conversion. In the example I gave, a built-in conversion goes on both sides. We can also introduce explicit nullable value type conversions in this way if the types are value types. – Eric Lippert Feb 26 '18 at 22:05
  • @EricLippert This is really interesting, I didn't know you could chain user-defined and built-in casts this way. It must be a headache to debug if it ever happens in real code, tho. – Arturo Torres Sánchez Feb 26 '18 at 22:09
  • 3
    @ArturoTorresSánchez: I can assure you it was a headache to implement. Particularly since there are bugs in the algorithm that must be maintained for backwards compatibility with existing programs that should have been errors. See the sources for details; I left copious notes. :-) – Eric Lippert Feb 26 '18 at 22:12
  • 20
    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
  • Downcasts may indicate some kind of design problem someplace, but not necessarily at the point of the cast. Something like the downcast in Enumerable<T>.Count, for example, is necessary because IEnumerable<T> lacks a means of asking whether the underlying collection knows its count, or asking the collection for its count if it knows. – supercat Feb 26 '18 at 22:59
  • 15
    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
  • 1
    @supercat: That's a good point which I decided to not go into here. The use cases for is and as tests are those where you're legit not sure what the type is, and you can maybe do something special if you can find out at runtime, whereas I think of downcasts as being used when you have a proof that the expression will be of the given type. – Eric Lippert Feb 26 '18 at 23:15
  • 2
    @EricLippert this was fascinating; is any of this easily accessible elsewhere? Given that comments are temporary, it would be awesome to be able to refer back to this reliably. – Dan Oberlam Feb 27 '18 at 00:35
  • 5
    @Dannnno: These sorts of things have come up on my blog from time to time. This series in particular is germane to this discussion: https://blogs.msdn.microsoft.com/ericlippert/2007/04/16/chained-user-defined-explicit-conversions-in-c/ https://blogs.msdn.microsoft.com/ericlippert/2007/04/18/chained-user-defined-explicit-conversions-in-c-part-two/ https://blogs.msdn.microsoft.com/ericlippert/2007/04/20/chained-user-defined-explicit-conversions-in-c-part-three/ https://blogs.msdn.microsoft.com/ericlippert/2009/10/08/whats-the-difference-between-as-and-cast-operators/ and so on. – Eric Lippert Feb 27 '18 at 00:37
  • @EricLippert: I generally love your answers and your insights (in fact just today I saw another one of your blog posts I loved), but this may be the first time I vehemently disagree with you. I'm completely shocked that you wrote "it is not maybe a code smell. It is definitely a code smell" -- I hope I'm missing or misunderstanding something, because it just seems so wrong. Are you seriously saying that implementing/overriding, say, object.Equals(object) is now considered a code smell? – user541686 Feb 27 '18 at 10:06
  • 2
    @Mehrdad No, he isn’t. He’s saying that the existence of object.Equals(object) inside System.Object is code smell. But given that that’s now its signature (rather than a generic one), implementing it isn’t code smell. See also supercat’s previous comment. – Konrad Rudolph Feb 27 '18 at 10:10
  • @KonradRudolph: That's absurd though. And focusing on object.Equals's existence (which C++ists like you and me would argue against) is completely missing the point. It's impossible to have an Superclass.Equals method that works polymorphically without having downcasts somewhere, so you(/Eric) are saying that merely being able to check for equality of two objects whose exact derived types you do not know at the point of the call is a code smell? And the same with Stream.EndRead, Superclass.Clone, etc. -- all of those are signs of code smells somewhere in the codebase? Really? – user541686 Feb 27 '18 at 10:53
  • 2
    @Mehrdad Why would it be absurd? — And checking for polymorphic equality works perfectly well without downcasting if you’re willing to go the extra mile and implement an appropriate visitor. — Of course most people wouldn’t be willing to go the extra mile but (a) that’s why Eric said C# is written by pragmatic people for pragmatic people, and (b) you very rarely need to do this; in ~99% of cases, either a simple, statically typed function or a virtual function is the correct way of implementing equality comparison. – Konrad Rudolph Feb 27 '18 at 11:02
  • @KonradRudolph: It's absurd because it's literally the accepted way (and I believe the only way) to implement methods like these polymorphically, and these methods are implemented, used, and benefited from this way all the time in real code, so saying that their existence is a code smell makes no sense. How would you implement visitors for Equals or Clone? Maybe it's my lack of sleep betraying me right now but I'm not seeing how you'd do that here. (I don't even see how to do visitors with EndRead to be honest, but that might be just me, just focus on the previous ones.) – user541686 Feb 27 '18 at 11:10
  • @Mehrdad Would you post object.Equals as an answer to the question (instead of only a comment), or shall I do it? – ChrisW Feb 27 '18 at 11:15
  • @ChrisW: Good idea, I'll do it, give me a few minutes! – user541686 Feb 27 '18 at 11:16
  • @ChrisW: Posted. And I'll add more as I think of them. – user541686 Feb 27 '18 at 11:25
  • @Mehrdad It’s hard to prove a negative. Why don’t you give me an example hierarchy that you think cannot implement Equals and Clone without downcasting and I’ll see what I can do? – Konrad Rudolph Feb 27 '18 at 11:39
  • @KonradRudolph: Sure, there's some in my answer. – user541686 Feb 27 '18 at 11:39
  • @KonradRudolph: While you're busy with that, a couple more notes: (1) Even with the visitor pattern, I believe the only way you can avoid a downcast is if the visitor interface exhaustively lists all the types that its implementers could handle a priori. But the classes that it can visit need to know about the visitor, too. So that means the classes in the hierarchy must all indirectly know about each other at compile-time... which means they aren't run-time polymorphic at all! Which means you probably would have a hard time justifying polymorphic inheritance, especially in C++. (cont'd) – user541686 Feb 27 '18 at 12:45
  • @KonradRudolph: ...(cont'd) So, based on this, I could probably go so far as to actually argue that cast-less visitors in C++ would be an anti-pattern, and that you should really be using discriminated unions, and the only reason it would be acceptable in C# is if the language isn't powerful enough to make that practical. (2) It's not really proving a negative -- if you call this a "code smell", that doesn't merely mean "∃ a better approach". Rather, it implies someone ought to know of a better approach to tackle or avoid the problem. If not, you can't declare it a code smell! – user541686 Feb 27 '18 at 12:51
  • 13
    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
  • 5
    Also, let's stop and think about what a code smell is. It's a pattern that warrants extra scrutiny because it is easily abused, confusing or often wrong in a subtle way. That's equality in a nutshell. When I was at Coverity I redesigned several of the equality mistake detectors because there are just so many ways to implement equality wrong. Yes it is a code smell; you should thoroughly review your implementations of Equals, and try to not stray far from accepted patterns. – Eric Lippert Feb 27 '18 at 14:56
  • @Eric I'd be interested on reading up on the requirement to break symmetry/reflexivity/transitivity for the equality operator, if you've got anything. – Rawling Feb 27 '18 at 15:02
  • 7
    @Rawling: Well that's easy. Is it a requirement that a.Equals(b) == b.Equals(a) always successfully compute to true? Or is attaining that actually impossible? Because in every implementation of Equals on a reference type I've ever seen, a.Equals(b) is false if b is null, but b.Equals(a) crashes. – Eric Lippert Feb 27 '18 at 15:09
  • 3
    And even without null, if you're implementing Frob and you decide that some Frobs are equal to some Blobs, then achieving frob.Equals(blob) == blob.Equals(frob) requires you to coordinate with the author of Blob, who might not even be alive anymore. The fundamental problem is that equality should be a multimethod, but is implemented as either a static method or a single dispatch virtual method, and that then imposes all of the knock-on problems. – Eric Lippert Feb 27 '18 at 15:11
  • @EricLippert, do you think equality of values from different types is reasonable to begin with? Wouldn't it be better to convert to the same type and then compare? – Arturo Torres Sánchez Feb 27 '18 at 15:27
  • 3
    @ArturoTorresSánchez: That's a reasonable idea but it quickly runs into problems. If Animal implements an equality operator on Animals and derived class Giraffe implements an equality operator on Giraffe then what happens when you try to compare a Giraffe with a Tiger? Hopefully both implementations agree that they are unequal, but which implementation should get used? – Eric Lippert Feb 27 '18 at 15:31
  • 2
    @ArturoTorresSánchez: In general I agree with you though. Implementing equality amongst dissimilar types seems like a code smell to me. I would be inclined to implement it as you suggest: implement equality on identical types, and convert one type to another. However that then leads to its own set of problems. Suppose we have PolarComplexNumber and CartesianComplexNumber structs. Do you want to have to say pol.Equals((PolarComplexNumber)car)? Seems excessively wordy. – Eric Lippert Feb 27 '18 at 15:36
  • And then it just gets weird when you add in == operators. Should pol==car call PolarComplexNumber.op_Equality or CartesianComplexNumber.op_Equality? If both exist then the expression is ambiguous. – Eric Lippert Feb 27 '18 at 15:36
  • @EricLippert I see. This really is a complex problem to think about. Really interesting nonetheless. – Arturo Torres Sánchez Feb 27 '18 at 15:42
  • I have to do this a lot with framework objects called by reflection and casting to interfaces. – Jake Feb 27 '18 at 18:26
  • @EricLippert: If such an Equals is so horrid (and note that the point here wasn't object having an Equals method, but a base class* having a virtual Equals method), then how exactly would you implement structural equality [like this*](https://softwareengineering.stackexchange.com/questions/366592/what-is-a-proper-use-of-downcasting/366666?noredirect=1#comment799963_366666) without downcasts? – user541686 Feb 27 '18 at 20:53
  • 4
    @Mehrdad: You're overstating my case. I'm not saying "never downcast". I'm saying when possible to avoid cheaply, do so, and when not possible, review your code extremely carefully because you are now running with scissors. Downcasting is turning off a safety system. The points where you turn off a safety system are precisely those where you need to take extra care to ensure correctness. – Eric Lippert Feb 27 '18 at 21:08
  • @EricLippert: Two points: (1) [Minor] It's not turning off a safety system at all though -- if the downcast fails then it is guaranteed to error. (2) [Major] Your answer comes across much more strongly than your comment. My point has been that "code smell" implies "this isn't the default solution, so you need to do a case-by-case analysis", whereas here I'm pointing out here that there are entire classes of problems (those for which you use virtual Equals, Clone, EndRead, etc.) for which the default (and only?) solution requires downcasting, so it itself can't be a code smell... – user541686 Feb 27 '18 at 22:30
  • @EricLippert: Otherwise, you would have to classify everything (classes, inheritance, polymorphism, generics, etc.) as a code smell, because they're all misusable, rendering it meaningless. But, I would be more than happy to endorse more nuanced positions on when it's a code smell. For example, I would say that "downcasting and then calling a virtual method" is a code smell -- it might make sense in obscure cases, but it 100% warrants a case-by-case analysis. Same with "downcasting to your own class's subclass" -- that also doesn't make sense in general. And so on and so on. – user541686 Feb 27 '18 at 22:31
  • @EricLippert: For what it's worth, the specific passages that I'm disputing in your answer are where you say downcasting is "definitely a code smell" and that "Downcasts were added to the language for those circumstances where it is hard and expensive to thus refactor the program." If you change them to convey that you do agree downcasting is the correct solution to some problems (and not merely the one that people are forced to choose due to time/cost constraints) then I could agree with your answer. – user541686 Feb 27 '18 at 23:53
35

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.

mmathis
  • 5,468
  • 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
  • @Joker_vD Whether you queue or not depends on your application. And whether queues are type erased or strongly typed also depends on the application. For WinForms callbacks there needs to be a time (but it can be deep inside the library) where type erased callbacks are stored in a weakly typed queue, true. – Konrad Rudolph Feb 27 '18 at 10:30
  • @KonradRudolph : But the question is, is type erasure dependent on queueing? Is there a way to avoid the downcast in all 4 combinations, and if so, what is it? As one option seems to be to have separate queues for each event type, but that leads to a ton of boilerplate. – The_Sympathizer Jan 07 '21 at 14:39
  • @The_Sympathizer Well. As I said, type erasure is needed. But it needn’t bleed into the user API. Both high-level event handlers and the event invocation code code be strongly typed; and type erasure would happen inside the WinForms library code. – Konrad Rudolph Jan 07 '21 at 19:06
18

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.

Philipp
  • 23,306
  • 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. – user253751 Feb 27 '18 at 21:33
  • @user2023861: dynamic still downcasting, just slower. – user541686 Feb 28 '18 at 18:53
  • Does anyone know a statically typed OOP language that manages to avoid this? It would be interesting to see how they do it. – aiwl Oct 27 '23 at 13:17
15

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.

user541686
  • 8,102
  • 15
    "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
  • 6
    @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. – user541686 Feb 27 '18 at 12:54
  • 5
    @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
  • Equals is suspicious, because you switch between reference equality and value equality, or if you have an implementation with value equality all the way down you end up with new C().Equals(new B()) returning true because the A parts match. That might be wrong, or it might be right, but it will likely confuse someone – Caleth Feb 27 '18 at 13:21
  • @Caleth: What in the world are you talking about? How could new C().Equals(new B()) return true? Equals is something that needs to be overridden in subclasses to work correctly. Downcasts are irrelevant to this. – user541686 Feb 27 '18 at 13:23
  • Stream is suspicious, because you assume that no-one will ever pass an IAsyncResult from anywhere else – Caleth Feb 27 '18 at 13:23
  • how do you implement Equals for class C : B and class D : B, such that you surprise no-one? – Caleth Feb 27 '18 at 13:25
  • 1
    @Caleth: Equals and Stream are suspicious now?! Have you never programmed in C#...?! I was busy writing you a much more nuanced response, but your claims are getting too preposterous for me to continue writing that response anymore. Yeah, sure, my designs are awful. Sorry, I don't know better. So how about you enlighten me how you would "implement Equals such that you surprise no-one" without downcasts? I'm all eyes/ears and I would love to learn from your perfect solution. – user541686 Feb 27 '18 at 13:28
  • 2
    Yes, they are suspicious. No, they are not bad. In an ideal world the type system would stop you from passing an IAsyncResult to EndRead that came from a difference instance of Stream's BeginRead. In a less ideal world (which is still better than what we have) the type system would stop you passing an IAsyncResult that came from a different subclass of Stream's BeginRead. – Caleth Feb 27 '18 at 13:38
  • 1
    And I wouldn't have a global polymorphic Equals at all. Where I needed a specific (potentially polymorphic) equivalence relation, I would use an instance of IEqualityComparer<B> that fit those specific needs – Caleth Feb 27 '18 at 13:41
  • @Caleth: OK, so here's the usual polymorphic example. How would you use IEqualityComparer polymorphically while avoiding downcasts like you claim? class Expression { abstract bool Equals(Expression other); } class AdditionExpression : Expression { ... } class SubtractionExpression : Expression { ... } void Main() { var evaluated = new Dictionary<Expression, Value>(); /* proceed to discover & evaluate expressions */} – user541686 Feb 27 '18 at 13:57
  • 1
  • 1
    Alright, I’ve reimplemented your code without downcasting (note the discussion!). But now I have to say that, although the code gives a solution that works without downcasting, I’m no longer convinced that downcasting is generally avoidable. It’s certainly not practically avoidable. – Konrad Rudolph Feb 27 '18 at 15:22
  • 4
    A classical case of "If all you have is a hammer, everything looks like a nail". What this demonstrates is that C#'s type system is too limited to fully express the intention here. object.Equals in Java or .NET is one of its worst design decisions (shortly after the whole clone() debacle). You might want to look at say Haskell or similar languages to demonstrate ways to solve the issues at hand quite nicely. – Voo Feb 27 '18 at 17:15
  • @Voo: Way to criticize with something completely irrelevant. I'm only answering the question of when you would want to downcast, not evangelize for Haskell and tell the C# designers how comparably awful their type system is. And like I said, if you dislike object.Equals(object), pretend it's not there and we have abstract bool A.Equals(A) instead. – user541686 Feb 27 '18 at 20:44
  • 1
    @KonradRudolph: Thanks! But you're not reimplementing the same functionality -- the entire caveat here was that Equals needs to be polymorphic. If you always know the derived type of whatever you're comparing then obviously you can downcast, but my entire point was that's frequently not the case, e.g. in the classic Expression example. (Unless you're handling that and I'm misunderstanding?) – user541686 Feb 27 '18 at 20:56
  • 7
    @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
  • @KonradRudolph You cannot treat all downcasts with suspicion simply because there are too many that are acceptable. It would be like turning on the most pedantic warnings in an existing project - unless there are already no pedantic warnings (because you fixed them all), it tends to drown out the more important warnings that really should notice. – user253751 Feb 27 '18 at 21:36
  • @immibis: thanks, I think your first sentence summed it up perfectly. – user541686 Feb 27 '18 at 22:40
  • @Voo: a language feature being a code smell does not mean the language designer might have a problem in their design. It means you might have a problem in your design. At this point you're outputting such utter nonsense that I can't take you seriously anymore. It's very clear you're saying something just for the sake of having said something and left your mark. – user541686 Feb 27 '18 at 22:48
  • @KonradRudolph: oops, just noticed a typo in my reply (though I'm guessing you already noticed): "then obviously you can downcast" was supposed to say "then obviously you can avoid* a downcast"*. – user541686 Feb 27 '18 at 22:58
  • 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 08:31
  • @Mehrdad Right, that’s why I wanted a concrete example … my code solves the problem posed in your answer (while trying to use a more illustrative example). Yes, something like the Expression pattern isn’t solved by this, as I discussed in the document accompanying the code. That would be solved by a visitor (and this is in fact the “established solution” there), with all the caveats you mentioned (i.e. visitors work well on sum types, but are hard to extend). – Konrad Rudolph Feb 28 '18 at 08:46
  • 1
    @immibis I’m not sure you’re making a good argument here because I always work with the strictest set of warnings enabled (but obviously from the start) and I strongly argue that this is the correct way of working. That said, see Voo’s comment: Code smell points to an underlying problem, but it’s not necessarily fixable by the application programmer, similar to the usage of goto. – Konrad Rudolph Feb 28 '18 at 08:56
  • 2
    @Mehrdad It's simple: You seem to think that since object.Equals is part of the language it cannot be a code smell. By that argument using goto in C++ or with in JavaScript is great too. Yes as a pragmatic programmer we will have to accept code smells sometimes, but that doesn't mean they are not code smells. When using them, you better be particularly careful, it might also mean that you should rethink your design to avoid the problem in the first place. – Voo Feb 28 '18 at 09:11
  • 1
    @KonradRudolph: Yeah, basically. Great that we're on the same page. :-) And I should mention I'm also with you on the warning example not being the best one, I do try to enable most warnings too (which is why I only endorsed his first sentence and not the rest =P). But I do think while his point still stands -- see my comments here (multiple) on code smells and associated case-by-case analysis. There's just too many huge classes of problems in which a downcast is the accepted solution to call it a code smell. – user541686 Feb 28 '18 at 09:15
  • Please avoid extended discussions in the comments section. Additional comments should be taken to chat. Thank you. – maple_shaft Feb 28 '18 at 14:01
  • 1
    I was tempted to accept TKK's answer and/or mmathis's answer, but this answer (Equals especially) may be the most common and fundamental example. – ChrisW Mar 01 '18 at 10:54
  • Not good solution! – Parkhid Jan 04 '23 at 18:01
  • @Voo What other statically typed languages are able to avoid this problem? I'd love to see how Haskell deals with it, can you provide an example? – aiwl Oct 27 '23 at 13:51
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 09:39
5

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.

Alexander
  • 4,884
  • 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? https://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.

Ewan
  • 75,506
  • 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
  • 4
    @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. – user541686 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
  • 1
    @Ewan: No, it's missing the point. For starters, you don't even have the option of using dynamic_cast in C++ unless the class has virtual functions, so there doesn't exist any exactly equivalent functionality to C#'s, period. Second, if you avoid C++ features because of UB then you're gonna have to avoid incrementing your i's and dereferencing your p's too. And third, "downcasting" as a concept doesn't require a type-check; that's something extra that C#'s cast and C++'s dynamic_cast do. Oh, but the question was specific to C#, you complain? OK, but then your C++ note is irrelevant. – user541686 Feb 28 '18 at 11:16
  • @Mehrdad you can dynamic cast anything polymorphic in c++, so its a match for the c# objects in the example. The reason I mention c++ in a c# tagged question is its the historical reason for the 'down cast is slow' thinking, the new c# 'as' casting is faster but I didn't want to get into a huge discussion about which version of which language has performance issues with which type of down casting. Down casting is traditionally a slow operation. – Ewan Feb 28 '18 at 13:18
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 ;-)

puck
  • 181
  • 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). – user541686 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.

RubberDuck
  • 8,961
  • 5
  • 35
  • 44
  • 1
    Not true for this particular example. You could just catch the NotImplementedException when calling SayGoodbye. – Per von Zweigbergk Feb 28 '18 at 07: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;
}
zwcloud
  • 131
  • 5