I understand that non-virtual methods are statically bound, which means, as far as I understand, that its known at compile-time itself as to which method will be invoked on which object. This decision is taken based on the static type of the object(s). What confuses me is interfaces (rather than class) and static binding.
Consider this code,
public interface IA
{
void f();
}
public class A : IA
{
public void f() { Console.WriteLine("A.f()"); }
}
public class B : A
{
public new void f() { Console.WriteLine("B.f()"); }
}
B b = new B();
b.f(); //calls B.f() //Line 1
IA ia = b as IA;
ia.f(); //calls A.f() //Line 2
Demo code: http://ideone.com/JOVmi
I understand the Line 1
. The compiler can know that b.f()
will invoke B.f()
because it knows the static type of b
which is B
.
But how does the compiler decide at compile-time itself that ia.f()
will call A.f()
? What is the static type of object ia
? Is it not IA
? But then that is an interface, and doesn't have any definition of f()
. Then how come it 开发者_高级运维works?
To make the case more puzzling, lets consider this static
method:
static void g(IA ia)
{
ia.f(); //What will it call? There can be too many classes implementing IA!
}
As the comment says, there can be too many classes that implement the interface IA
, then how can the compile statically decide which method ia.f()
would call? I mean, say if I've a class defined as:
public class C : A, IA
{
public new void f() { Console.WriteLine("C.f()"); }
}
As you see, C
, unlike B
, implements IA
in addition to deriving from A
. That means, we've a different behavior here:
g(new B()); //inside g(): ia.f() calls A.f() as before!
g(new C()); //inside g(): ia.f() doesn't calls A.f(), rather it calls C.f()
Demo code : http://ideone.com/awCor
How would I understand all these variations, especially how interfaces and static binding together work?
And few more (ideone):
C c = new C();
c.f(); //calls C.f()
IA ia = c as IA;
ia.f(); //calls C.f()
A a = c as A;
a.f(); //doesn't call C.f() - instead calls A.f()
IA iaa = a as IA;
iaa.f(); //calls C.f() - not A.f()
Please help me understanding all these, and how static binding is done by the C# compiler.
But how does the compiler decide at compile-time itself that
ia.f()
will callA.f()
?
It doesn't. It knows that ia.f()
will be calling IA.f()
on the object instance contained in ia
. It emits this call opcode and lets the runtime figure it out when the call is executed.
Here is the IL that will be emitted for the bottom half of your example code:
.locals init (
class B V_0,
class IA V_1)
IL_0000: newobj instance void class B::'.ctor'()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: callvirt instance void class B::f()
IL_000c: ldloc.0
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: callvirt instance void class IA::f()
IL_0014: ret
Note that callvirt
is used in both cases. This is used because the runtime is able to figure out on its own when the target method is non-virtual. (Additionally, callvirt
performs an implicit null check on the this
argument, while call
does not.)
This IL dump should answer all of your other questions. In short: the compiler doesn't even attempt to resolve the final method call. That's a job for the runtime.
Static binding means something else than you think. Also called 'early binding', it is the opposite of late binding, available in C# version 4 with the dynamic keyword and in all versions with reflection. The main characteristic of late binding is that the compiler cannot verify that the called method even exists, let alone verify that the proper arguments are passed. If something is a-miss, you'll get a runtime exception. It is also slow because the runtime needs to do extra work to lookup the method, verify the arguments and construct the call stack frame.
This is not an issue when you use interfaces or virtual methods, the compiler can verify everything up front. The resulting code is very efficient. This still results in indirect method calls (aka 'dynamic dispatch'), required to implement interfaces and virtual methods, but still used in C# for non-virtual instance methods as well. Documented in this blog post from a former C# team member. The CLR plumbing that makes this work is called a 'method table'. Roughly analogous to a v-table in C++, but the method table contains an entry for every method, including non-virtual ones. An interface reference is simply a pointer into this table.
精彩评论