C# – a proper use of downcasting

cobject-oriented

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?

Best Answer

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.