Чому методи інтерфейсу C # не оголошуються абстрактними або віртуальними?


108

Методи C # в інтерфейсах оголошуються без використання 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в ІЛ.

Відповіді:


145

Для інтерфейсу додавання ключових слів 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, навіть якщо компілятор повинен пройти обручі, щоб це зробити.


73

Цитуючи Джефрі Рітчера з CLR через CSharp 3rd Edition тут

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


25
Цитата не говорить про те, чому потрібно застосовувати метод інтерфейсу для позначення віртуального. Це тому, що це поліморфно щодо типу інтерфейсу, тому йому потрібен проріз на віртуальній таблиці, щоб дозволити віртуальний метод диспетчеризації.
Йордао

1
Мені заборонено явно позначати метод інтерфейсу як віртуальний та отримувати помилку "помилка CS0106: Модифікатор" віртуальний "недійсний для цього елемента". Тестовано за допомогою v2.0.50727 (найстаріша версія на моєму ПК).
ccppjava

3
@ccppjava З коментаря Джорадо нижче, ви позначаєте члена класу, який реалізує віртуальний інтерфейс, щоб дозволяти підкласам заміняти клас.
Крістофер Стівенсон

13

Так, методи реалізації інтерфейсу є віртуальними, що стосується часу виконання. Це деталь реалізації, вона змушує інтерфейси працювати. Віртуальні методи отримують слоти в v-таблиці класу, кожен слот має вказівку на один із віртуальних методів. Кастинг об'єкта на тип інтерфейсу генерує вказівник на розділ таблиці, який реалізує методи інтерфейсу. Код клієнта, який використовує посилання на інтерфейс, тепер бачить перший вказівник методу інтерфейсу при зміщенні 0 від покажчика інтерфейсу etcetera.

Те, що я недооцінював у своїй оригінальній відповіді, - це значення кінцевого атрибута. Це не дозволяє похідному класу перекрити віртуальний метод. Похідний клас повинен повторно реалізувати інтерфейс, методи реалізації затіняють методи базового класу. Що достатньо для реалізації мовного договору C #, який говорить про те, що метод реалізації не є віртуальним.

Якщо ви оголосите метод Dispose () у класі Example як віртуальний, ви побачите видалення остаточного атрибута. Тепер дозволено похідному класу перекрити його.


4

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

Однак у .NET інтерфейси не реалізуються як окремі vtables для кожного класу. Методи інтерфейсу індексуються через таблицю методів глобального інтерфейсу, до складу якої входять усі інтерфейси. Тому не потрібно оголошувати метод віртуальним для того, щоб цей метод реалізував метод інтерфейсу - таблиця методів глобального інтерфейсу може просто вказувати на кодову адресу методу класу.

Оголошення методу віртуальним для реалізації інтерфейсу не потрібно в інших мовах, навіть у не-CLR платформах. Мова Delphi на Win32 - один із прикладів.


0

Вони не віртуальні (з точки зору того, як ми думаємо про них, якщо не з точки зору основної реалізації як (запечатаний віртуальний) - добре прочитати інші відповіді тут і навчитися щось самому :-)

Вони нічого не перекривають - в інтерфейсі немає реалізації.

Інтерфейс - це надання "контракту", якого повинен дотримуватися клас - шаблону, якщо вам подобається, щоб абоненти знали, як викликати об'єкт, навіть якщо вони ніколи раніше не бачили цього конкретного класу.

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


всі в цій темі знають, для чого призначені інтерфейси. Питання надзвичайно специфічне - IL-генерація є віртуальною для методу інтерфейсу, а не віртуальною для неінтерфейсного методу.
Рекс М

5
Так, реально легко критикувати відповідь після редагування питання, чи не так?
Джейсон Вільямс

0

Інтерфейси є більш абстрактним поняттям, ніж класи, коли ви оголошуєте клас, який реалізує інтерфейс, ви просто говорите: "клас повинен мати ці конкретні методи з інтерфейсу, і не має значення коли статичний , віртуальний , невіртуальний , переопределений , як до тих пір, поки він має однаковий ідентифікатор та параметри типу ".

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

Але, можливо, ви маєте рацію, я думаю, може бути хорошою ідеєю мати в інтерфейсах певний атрибут "віртуальна" / "заміна", якщо ви хочете обмежити методи класів, які реалізують певний інтерфейс. Але це також означає мати "невіртуальне", "dontcareifvirtualornot" ключові слова для обох інтерфейсів.

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


@override насправді не змінює поведінку коду або не змінює отриманий байт-код. Що це робить, це сигналізувати компілятору про те, що настільки прикрашений метод призначений для переопрацювання, що дозволяє компілятору зробити деякі перевірки правильності. C # працює інакше; override- це першокласне ключове слово в самій мові.
Роберт Харві
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.