Не виртуальные методы, статическое связывание и интерфейс в C#

Я понимаю, что не виртуальные методы статически связаны, что означает, насколько я понимаю, что во время компиляции известно, какой метод будет вызываться для какого объекта. Это решение принимается на основе статического типа объекта (ов). Меня смущает интерфейс (а не класс) и статическая привязка.

Рассмотрим этот код,

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

Демо-код: http://ideone.com/JOVmi

Я понимаю Line 1, Компилятор может знать, что b.f() вызовет B.f() потому что он знает статический тип b который B,

Но как компилятор решает во время компиляции, что ia.f() позвоню A.f()? Что такое статический тип объекта ia? Это не IA? Но тогда это интерфейс, и не имеет никакого определения f(), Тогда как же это работает?

Чтобы сделать случай более загадочным, давайте рассмотрим это static метод:

static void g(IA ia)
{
   ia.f(); //What will it call? There can be too many classes implementing IA!
}

Как говорится в комментарии, может быть слишком много классов, которые реализуют интерфейс IAтогда как компиляция может статически решить, какой метод ia.f() назвал бы? Я имею в виду, скажем, если у меня есть класс, определенный как:

public class C : A, IA 
{
    public new void f() { Console.WriteLine("C.f()"); }
}

Как вы видите, C, В отличие от B, реализует IA в дополнение к получению из A, Это означает, что у нас другое поведение здесь:

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()

Демо-код: http://ideone.com/awCor

Как бы я понял все эти варианты, особенно как работают интерфейсы и статическое связывание?

И еще несколько ( 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()

Пожалуйста, помогите мне понять все это, и как статическое связывание выполняется компилятором C#.

2 ответа

Но как компилятор решает во время компиляции, что ia.f() позвоню A.f()?

Это не так. Это знает что ia.f() буду звонить IA.f() на экземпляре объекта, содержащегося в ia, Он выдает этот код операции вызова и позволяет среде выполнения выяснить это при выполнении вызова.

Вот IL, который будет испущен для нижней половины вашего примера кода:

    .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

Обратите внимание, что callvirt используется в обоих случаях. Это используется, потому что среда выполнения может самостоятельно определять, когда целевой метод не виртуален. (Дополнительно, callvirt выполняет неявную нулевую проверку на this аргумент, в то время как call не.)

Этот дамп должен ответить на все ваши другие вопросы. Вкратце: компилятор даже не пытается разрешить последний вызов метода. Это работа для времени выполнения.

Статическое связывание означает нечто иное, чем вы думаете. Также называется "раннее связывание", оно противоположно позднему связыванию, доступно в C# версии 4 с ключевым словом dynamic и во всех версиях с отражением. Основная характеристика позднего связывания заключается в том, что компилятор не может проверить, что вызываемый метод даже существует, не говоря уже о том, что переданы правильные аргументы. Если что-то не так, вы получите исключение во время выполнения. Это также медленно, потому что среда выполнения должна выполнять дополнительную работу для поиска метода, проверки аргументов и построения фрейма стека вызовов.

Это не проблема, когда вы используете интерфейсы или виртуальные методы, компилятор может проверить все заранее. Полученный код очень эффективен. Это все еще приводит к косвенным вызовам методов (также называемым "динамическая диспетчеризация"), которые необходимы для реализации интерфейсов и виртуальных методов, но все еще используются в C# для не виртуальных методов экземпляра. Документировано в этом блоге от бывшего члена команды C#. Сантехника CLR, которая выполняет эту работу, называется "таблицей методов". Примерно аналогично v-таблице в C++, но таблица методов содержит запись для каждого метода, включая не виртуальные. Ссылка на интерфейс - это просто указатель на эту таблицу.

Другие вопросы по тегам