Почему методы интерфейса С# не объявлены абстрактными или виртуальными?

Методы С# в интерфейсах объявляются без использования ключевого слова virtual и переопределяются в производном классе без использования ключевого слова override.

Есть ли причина для этого? Я предполагаю, что это просто удобство на языке, и, очевидно, CLR знает, как справиться с этим в рамках обложки (методы по умолчанию не являются виртуальными), но есть ли другие технические причины?

Вот IL, который производный класс генерирует:

class Example : IDisposable {
    public void Dispose() { }
}

.method public hidebysig newslot virtual final 
        instance void  Dispose() cil managed
{
  // Code size       2 (0x2)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ret
} // end of method Example::Dispose

Обратите внимание, что метод объявлен virtual final в IL.

Ответ 1

Для интерфейса добавление ключевых слов abstract или даже public будет избыточным, поэтому вы опустите их:

interface MyInterface {
  void Method();
}

В CIL метод отмечен virtual и abstract.

(Обратите внимание, что Java позволяет объявлять члены интерфейса public abstract).

Для класса реализации есть несколько вариантов:

Non-overridable. В С# класс не объявляет метод как virtual. Это означает, что он не может быть переопределен в производном классе (только скрытый). В CIL метод по-прежнему является виртуальным (но запечатанным), поскольку он должен поддерживать полиморфизм в отношении типа интерфейса.

class MyClass : MyInterface {
  public void Method() {}
}

Overridable: как в С#, так и в CIL метод virtual. Он участвует в полиморфной отправке, и его можно переопределить.

class MyClass : MyInterface {
  public virtual void Method() {}
}

Явно. Это способ для класса реализовать интерфейс, но не предоставлять методы интерфейса в открытом интерфейсе самого класса. В CIL метод будет private (!), Но он по-прежнему будет вызываться вне класса из ссылки на соответствующий тип интерфейса. Явные реализации также не являются допустимыми. Это возможно, потому что существует директива CIL (.override), которая свяжет частный метод с соответствующим методом интерфейса, который он реализует.

[С#]

class MyClass : MyInterface {
  void MyInterface.Method() {}
}

[КСС]

.method private hidebysig newslot virtual final instance void MyInterface.Method() cil managed
{
  .override MyInterface::Method
}

В VB.NET вы можете даже псевдоним имени метода интерфейса в классе реализации.

[VB.NET]

Public Class MyClass
  Implements MyInterface
  Public Sub AliasedMethod() Implements MyInterface.Method
  End Sub
End Class

[КСС]

.method public newslot virtual final instance void AliasedMethod() cil managed
{
  .override MyInterface::Method
}

Теперь рассмотрим этот странный случай:

interface MyInterface {
  void Method();
}
class Base {
  public void Method();
}
class Derived : Base, MyInterface { }

Если Base и Derived объявлены в той же сборке, компилятор сделает Base::Method виртуальным и запечатанным (в CIL), хотя Base не реализует интерфейс.

Если Base и Derived находятся в разных сборках, при компиляции сборки Derived компилятор не изменит другую сборку, поэтому он представит член в Derived, который будет явной реализацией для MyInterface::Method, который просто делегирует вызов Base::Method.

Итак, каждая реализация метода интерфейса должна поддерживать полиморфное поведение и поэтому должна быть помечена как виртуальная на CIL, даже если компилятор должен пройти через обручи, чтобы сделать это.

Ответ 2

Цитата Jeffrey Ritcher от CLR через CSharp 3rd Edition здесь

Для CLR требуется, чтобы интерфейс методы должны быть отмечены как виртуальные. если ты не указывать явным образом метод как виртуальный в вашем исходном коде, компилятор отмечает метод как виртуальный и запечатаны; это предотвращает класс от переопределения интерфейса метод. Если вы явно отметите метод как виртуальный, метки компилятора метод как виртуальный (и оставляет его вскрыты); это позволяет получить производный класс для переопределения метода интерфейса. Если метод интерфейса запечатан, производный класс не может переопределить метод. Однако производный класс может перенаследовать один и тот же интерфейс и обеспечить собственную реализацию интерфейсов.

Ответ 3

Да, методы реализации интерфейса являются виртуальными по отношению к времени выполнения. Это деталь реализации, она делает интерфейсы работы. Виртуальные методы получают слоты в v-таблице класса, каждый слот имеет указатель на один из виртуальных методов. Приведение объекта к типу интерфейса генерирует указатель на раздел таблицы, который реализует методы интерфейса. Клиентский код, который использует ссылку на интерфейс, теперь видит первый указатель метода интерфейса со смещением 0 от указателя интерфейса и т.д.

То, что я недооценил в своем первоначальном ответе, - это значение конечного атрибута. Это предотвращает переопределение производного класса по виртуальному методу. Производный класс должен повторно реализовать интерфейс, методы реализации теневого метода базового класса. Этого достаточно для реализации контракта на языке С#, который говорит, что метод реализации не является виртуальным.

Если вы объявите метод Dispose() в классе Example виртуальным, вы увидите, что окончательный атрибут будет удален. Теперь разрешаем производному классу переопределить его.

Ответ 4

В большинстве других скомпилированных кодовых сред интерфейсы реализуются как vtables - список указателей на тела метода. Обычно класс, реализующий несколько интерфейсов, будет где-то во внутреннем компиляторе сгенерированных метаданных списком интерфейсов vtables, один vtable для интерфейса (так что порядок метода сохраняется). Таким образом, как обычно реализуются интерфейсы COM.

Однако в .NET интерфейсы не реализованы как отдельные vtables для каждого класса. Методы интерфейса индексируются через таблицу методов глобального интерфейса, в которую входят все интерфейсы. Поэтому нет необходимости объявлять метод виртуальным для того, чтобы этот метод реализовал интерфейсный метод - таблица метода глобального интерфейса может просто указывать на адрес кода метода класса напрямую.

Объявление виртуального метода для реализации интерфейса не требуется на других языках, даже на платформах, отличных от CLR. Язык Delphi на Win32 - один из примеров.

Ответ 5

Они не являются виртуальными (в терминах того, как мы думаем о них, если не в терминах базовой реализации как (запечатанный виртуальный) - хорошо читать другие ответы здесь и сами чему-то учиться: -)

Они не переопределяют ничего - в интерфейсе нет реализации.

Весь интерфейс представляет собой поставку "контракта", к которому должен соответствовать класс - шаблон, если хотите, так что вызывающие абоненты знают, как вызвать объект, даже если они никогда раньше не видели этот класс.

Для этого класс должен реализовать интерфейсный метод, как он будет, в рамках контракта - виртуальный или "не виртуальный" (запечатанный виртуальный, как выясняется).

Ответ 6

<joke> Это то, что вы можете попросить Андерса Хейлсберга и остальную часть команды дизайна С#. </joke>

Интерфейсы являются более абстрактным понятием, чем классы, когда вы объявляете класс, который реализует интерфейс, вы просто говорите: "класс должен иметь эти конкретные методы из интерфейса, и не имеет значения, статичный, виртуальный, не виртуальный, переопределить, если у него одинаковые идентификаторы и параметры одного и того же типа.

Другие языки, поддерживающие такие интерфейсы, как Object Pascal ( "Delphi" ) и Objective-C (Mac), не требуют, чтобы методы интерфейса были помечены как виртуальные, так и не виртуальные.

Но, возможно, вы правы, я думаю, может быть хорошей идеей иметь определенный атрибут "virtual" / "override" в интерфейсах, если вы хотите ограничить методы классов, которые реализуют конкретный интерфейс. Но это также означает наличие "невиртуальных", "dontcareifvirtualornot" ключевых слов для обоих интерфейсов.

Я понимаю ваш вопрос, потому что я вижу что-то подобное в Java, когда метод класса должен использовать "@virtual" или "@override", чтобы убедиться, что метод должен быть виртуальным.