If I have two classes, A and B where B derives from A:
class A {}
class B : A { }
I can upcast quite happily an instance of B to A.
B b = new B();
A a = b;
Now, I can understand how the runtime can determine that the underlying type is B, as page 132 of the ECMA-335 (Common Language Infrastructure (CLI) Partitions I to VI) states
Objects of instantiated types shall carry sufficient information to
recover at runtime their exact type (including the types and number of
their generic arguments). [Rationale: This is required to correctly
implement casting and instance-of testing, as well as in reflection
capabilities
So, how does the runtime know that although the underlying type is B it is in fact stored in an A. I know for a fact that I won’t see the methods available on B, but if the underlying type is B, how does it store the type of the storage location A?
Does that make sense?
The object is a B, and the CLI knows it is a B. The thing that knows about A is mainly the compiler, which then declares the field or local as being of type A (or, in some cases like method chaining, the type is known via the return type of the method, which is stamped hard into the caller’s IL). So basically: the calling code itself is what says “I think this is an A”. The assignments etc are usually all verifiable in advance, so there is no risk of getting it wrong. If you try to hack the IL to deliberately get it wrong the runtime will tell you off and refuse to run the method. And for these reasons, the assignment of a known-to-be-B value to an A local/field does not require any type checking – it is just a direct assignment.