3

In C# I'm in a scenario with the following types provided by the environment:

public interface IFoo {
}

public abstract class Base {
}

public class Derived : Base, IFoo {
}

public class Arbitrary {
    public Base GetBase() { }
}

Here's what I've written in addition to this. Note that I can guarantee in my code that Arbitrary.GetBase() will always return an instance of Derived.

public class Arbitrary2 : Arbitrary {
    public IFoo GetDerived() {
        return (IFoo)base.GetBase();
    }
}

However this code fails with the message "Cannot convert type 'Base' to 'IFoo'".

But if I do this then it works:

public class Arbitrary2 : Arbitrary {
    public IFoo GetDerived() {
        Object baseAsObject = base.GetBase();
        return (IFoo)baseAsObject ;
    }
}

Why is this upcast to Object necessary before I downcast it to IFoo? The two pieces of code are functionally identical, and the latter will reliably crash at runtime if the cast is invalid. I don't understand why the compiler complains.

4

6 Answers 6

6

You don't need to do this. Your code should work, as is. See this program for details:

using System;

public interface IFoo { }

public abstract class Base { }

public class Derived : Base, IFoo { }

public class Arbitrary {
    public Base GetBase() { return new Derived(); }
}

public class Arbitrary2 : Arbitrary {
    public IFoo GetDerived() {
        return (IFoo)base.GetBase();
    }
}

class Program
{
    static void Main(string[] args)
    {
        Arbitrary2 test = new Arbitrary2();
        IFoo check = test.GetDerived();

        Console.WriteLine(check.GetType().Name);

        Console.WriteLine("Press key to exit:");
        Console.ReadKey();
    }
}
14
  • 1
    This wont fail at runtime. The OP states that GetBase returns Derived, which does implement IFoo Commented Jul 11, 2012 at 18:26
  • 1
    The compiler, however, doesn't know that. The only thing it knows is that Base does not implement IFoo. Commented Jul 11, 2012 at 18:27
  • @ChrisShain The compiler can't tell - Note that I said it will "likely" fail at runtime - while it may succeed, the compiler will assume that it's an actual "Base" object since anything else would be a violation of the Liskov Substitution Principle - any Base type should be valid there, which may or may not implement some arbitrary interface. Commented Jul 11, 2012 at 18:28
  • 1
    @David and Reed: I'm not sure the compiler treats Object in a special way. It just says "Object is assignable from IFoo, so there's the relation I require, so the cast shall compile." But with Base and IFoo the compiler says: "Neither of these two types is assignable from the other, so I must disallow the cast." Also see my new answer where I use a cast which I beleive is OK because Base is assignable from Derived. Not every Base is a Derived, though, so it could fail at runtime (David guarantees it won't). Commented Jul 11, 2012 at 21:17
  • 1
    @JeppeStigNielsen Actually - it doesn't matter - The original question is flawed, and my (old) answer was wrong. I edited to show fully working code, using just a simple cast. Commented Jul 11, 2012 at 21:51
1

Your GetBase() method returns Base, which does not implement IFoo.

1
  • That doesn't need to prevent it from compiling. It's possible (and indeed the case here) that the actual instance of the Base class is really a subclass that implements the interface. As long as the Base isn't sealed this will even compile (or at least it does for me on C# 3.5; not sure what versions it does/doesn't work on).
    – Servy
    Commented Jul 12, 2012 at 13:36
1

Base can't be converted to IFoo because Base doesn't have anything to do with IFoo:

public abstract class Base { }

Based on this declaration, there could very well be instances of Base which aren't of type IFoo. Indeed, based on this declaration the compiler has absolutely no reason to assume that any given instance of Base would ever implement IFoo.

Derived implements IFoo:

public class Derived : Base, IFoo { }

But you're not returning a Derived, you're returning a Base. By polymorphing it through Object you're effectively "tricking" the compiler. You're telling it that you know more than it does and it should listen to you. This can be fine, as long as you do in fact know more than the compiler does. And what you know that the compiler doesn't know is that every instance of Base is going to be able to polymorph to IFoo.

In that case, why not just implement IFoo on Base? That way you'd be sharing your knowledge with the compiler and everyone will be happy.

2
  • 1
    Good answer, though there is no "boxing" here, since the type being cast to object is not a value type.
    – dlev
    Commented Jul 11, 2012 at 18:29
  • @dlev: True, I didn't feel like it was the right term. I just didn't think of a better one. I'll change it to "polymorphing."
    – David
    Commented Jul 11, 2012 at 18:29
1

The reason the compiler disallows the explicit cast is that Base doesn't implement IFoo.

If you can guarantee that GetBase() will always return a Derived, then you can just insert a cast to Derived prior to the IFoo cast:

public class Arbitrary2 : Arbitrary {
    public IFoo GetDerived() {
        return (IFoo)(Derived)base.GetBase();
    }
}

Of course this will throw at run-time if you're mistaken. Alternatively, you can use an as cast will just return null if it fails:

public class Arbitrary2 : Arbitrary {
    public IFoo GetDerived() {
        return base.GetBase() as IFoo;
    }
}
1

Arbitrary.GetBase() returns an instance of Base. Base's hierarchy does not contain IFoo.

At runtime, yes, your object is a Derived instance, but based on what the compiler knows--class relationships--there isn't a connection and therefore a way to cast from Base to IFoo as you are trying in your first method.

1

Can't you just write

public class Arbitrary2 : Arbitrary {
    public IFoo GetDerived() {
        return (Derived)this.GetBase();
    }
}

The compiler will see the connection between Derived and Base, so the explicit cast to Derived ought to be OK. Then any Derived is surely an IFoo, so there's no need to cast an additional time (that conversion is implicit).

Don't use the base. keyword. Either say this. (as above) or leave out.

EDIT: Also, your original code did compile, but my version might be a bit easier to read.

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