Хороший вопрос.
Способ думать об этом: интерфейсы получают свой собственный набор слотов. Класс, который реализует интерфейс, необходим для заполнения этих слотов.
- Интерфейс I1 имеет слот, который мы назовем I1SLOT.
- Интерфейс I1 имеет слот, который мы назовем I2SLOT.
- Класс A имеет два собственных слота, AMinSLOT и ADrawSLOT.
- В классе A есть три метода, которые мы будем называть AMinMethod, ADrawMethod и AI2DrawMethod.
- Когда вы говорите «новый A», среда выполнения имеет четыре слота для заполнения.
- I1SLOT заполнен ADrawMethod.
- I2SLOT заполнен AI2DrawMethod.
- AMinSLOT заполняется с помощью AMinMethod.
- ADrawSLOT заполняется с помощью ADrawMethod.
- Класс B имеет три слота. Он наследует AMinSLOT и ADrawSLOT и определяет новый слот, BDrawSLOT.
- В классе B есть два метода, BDrawMethod и BI1DrawMethod.
- Когда вы говорите «new B», среда выполнения имеет пять слотов для заполнения.
- I1SLOT заполняется BI1DrawMethod.
- I2SLOT заполняется BDrawMethod.
- AMinSLOT заполняется с помощью AMinMethod.
- ADrawSLOT заполнен ADrawMethod.
- BDrawSLOT заполняется методом BDrawMethod.
Теперь запомните, задача разрешения перегрузки заключается в выборе слота на основе типа и аргументов. Аргументов нет, поэтому у компилятора есть только тот тип, из которого нужно отключиться.
- Когда вы вызываете Draw для объекта типа A времени компиляции, наилучшим совпадением является ADrawSLOT.
- Когда вы вызываете Draw для объекта типа B во время компиляции, наилучшим совпадением является BDrawSLOT.
- Когда вы вызываете Draw для объекта типа I1 времени компиляции, наилучшим совпадением будет I1SLOT.
- Когда вы вызываете Draw для объекта типа I2 времени компиляции, наилучшим совпадением будет I2SLOT.
И компилятор генерирует код, который говорит: «вызовите любой метод, который находится в выбранном слоте во время выполнения».
Подводя итог:
A a1 = new A();
A a2 = new B();
B b = new B();
(a1 as A).Draw(); // ADrawSLOT contains A::Draw
(a1 as I1).Draw(); // I1SLOT contains A::Draw
(a1 as I2).Draw(); // I2SLOT contains A::I2.Draw
(a2 as A).Draw(); // ADrawSLOT contains A::Draw
(a2 as B).Draw(); // BDrawSLOT contains B::Draw
(a2 as I1).Draw(); // I1SLOT contains B::I1.Draw
(a2 as I2).Draw(); // I2SLOT contains B::Draw
(b as A).Draw(); // ADrawSLOT contains A::Draw
(b as B).Draw(); // BDrawSLOT contains B::Draw
(b as I1).Draw(); // I1SLOT contains B::I1Draw
(b as I2).Draw(); // I2SLOT contains B::Draw
Если вам интересно, как это реализовано, используйте ILDASM для дизассемблирования вашей программы, а затем посмотрите на таблицу метаданных 25 (0x19), таблицу MethodImpl. MethodImplTable этой программы:
1 == 0:TypeDef[2000004], 1:MethodDefOrRef[06000005], 2:MethodDefOrRef[06000002]
2 == 0:TypeDef[2000005], 1:MethodDefOrRef[06000008], 2:MethodDefOrRef[06000001]
Затем вы можете посмотреть в таблицах typedef и methoddef, и вы увидите, что это декодируется как:
in type A the method A::I2.Draw implements the method I2::Draw
in type B the method B::I1.Draw implements the method I1::Draw
Таблица MethodImpl - это то, как CLI представляет понятие «мне нужно вставить в этот слот что-то, что отличается от того, что выбирают обычные правила сопоставления имен». Обычно правила сопоставления имен выбирают метод «Draw» для добавления в этот слот, а не «I1.Draw» или «I2.Draw».
Возможно, вы также захотите прочитать раздел 22.27 раздела II спецификации CLI.