Почему методы интерфейса C # не объявлены абстрактными или виртуальными? - PullRequest
101 голосов
/ 01 сентября 2010
Методы

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

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

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.

Ответы [ 6 ]

139 голосов
/ 01 сентября 2010

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

interface MyInterface {
  void Method();
}

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

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

Для реализующего класса есть несколько параметров:

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

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

Переопределяемый : как в C #, так и в CIL метод имеет вид virtual.Он участвует в полиморфной диспетчеризации и может быть переопределен.

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

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

[C #]

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

[CIL]

.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

[CIL]

.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, даже если компилятор должен пройти через обручи, чтобы сделать это.

71 голосов
/ 01 сентября 2010

Цитата Джеффри Ритчера из CLR через CSharp 3rd Edition здесь

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

11 голосов
/ 01 сентября 2010

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

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

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

4 голосов
/ 01 сентября 2010

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

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

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

0 голосов
/ 26 марта 2011

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

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

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

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

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

0 голосов
/ 01 сентября 2010

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

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

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

Затем класс должен реализовать метод интерфейса так, как он будет в рамках контракта - виртуальный или «не виртуальный» (как оказалось, виртуальный с запечатыванием).

...