What exactly is a CLR reference, and how does it hold type information?
My brain had a segfault this morning trying to understand exactly how and when C# can work out the type of an object from a reference to that object. Consider the following highly unoriginal example code:
class Foo { public virtual void Baz() { } }
class Bar : Foo { }
class Program {
static void Main() {
Foo f = new Bar();
f.Baz();
}
}
The type of the reference there is Foo, but the object instance actually created is a Bar. That Bar instance has some overhead, i.e. a Sync Block index, and a reference to a MethodTable, presumably Bar's MethodTable. If you look at the Bar object on the heap, the only clue to its type is the MethodTable reference, which would suggest that it's a Bar.
Onto the question then. Is there any way for C# to know from the actual object graph that 'f' is a Foo, and if so, how? Does the reference 'f' contain type information itself? When I call f.Baz(), am I right in thinking that the dispatch occurs via Bar's MethodTable anyway? Is it simply that case that the C# compiler uses flow analysis to work out what's going on and prevent any illegal opera开发者_如何学Ctions? Does the CLR actually not care about the type declaration of Foo by the time it's been translated into IL?
Apologies if that's a long-winded and poorly-phrased question - let me know if any clarification is required!
TL;DR - How does a polymorphic reference in the CLR work? How is any discrepancy between the actual vs declared class type persisted, and could you tell what the original declaration was from the resultant IL?
You’re thinking way too complicated.
Does the reference 'f' contain type information itself?
No. It doesn’t have to. It is simply an address to the beginning of the Bar
object’s memory that was constructed earlier. That object contains a virtual method table (and possibly a reference to its associated Type
object1, but that’s irrelevant here).
When I call f.Baz(), am I right in thinking that the dispatch occurs via Bar's MethodTable anyway?
Yes.
Is it simply that case that the C# compiler uses flow analysis to work out what's going on and prevent any illegal operations?
Flow analysis is complex and completely unnecessary here. The compiler allows exactly those operations that are allowed for the declaration type of f
– which is Foo
. The compiler doesn’t care about the actual (= dynamic) type of f
at all.
could you tell what the original declaration was from the resultant IL?
Depends. An object doesn’t have a static type so “telling its static type at runtime” is meaningless. Declarations, on the other hand, differ. If the variable is a formal parameter of a method then sure, you can (at runtime) use reflection to determine the declaration type of the method’s parameter.
For local variables, once again this operation is meaningless. On the other hand, the IL does store this information (as metadata?) via .locals
so the code can theoretically be reverse engineered (Cecil and Reflector do this) to get the static type of variables.
1 I’m guessing here but this is actually improbable. If each object held its own reference to an associated Type
object, that would mean an additional overhead of a pointer. Additionally, this reference is entirely unnecessary, since the object can simply call GetType
on itself to get its type. GetType
does only need to be implemented once for each class (kind of a static method, really) and is dispatched via the usual virtual function table. Thus only one reference to a Type
is required per class, as opposed to per object.
Is there any way for C# to know from the actual object graph that 'f' is a Foo
That is statically known to the compiler
Does the reference 'f' contain type information itself?
Interesting, it must be contained in the IL somehow.
When I call f.Baz(), am I right in thinking that the dispatch occurs via Bar's MethodTable anyway?
Yes. And that is found through the instance that f points to.
精彩评论