Почему виртуальный разрешен при реализации методов интерфейса?
У меня есть один конкретный запрос с интерфейсами. По умолчанию методы интерфейса абстрактны и виртуальны, поэтому, если мы реализуем этот интерфейс и предоставляем определение в классе, мы фактически переопределяем этот метод, но когда мы снова помещаем этот метод как виртуальный в классе реализации, почему компилятор не считает, что мы действительно пытаемся чтобы скрыть виртуальный метод исходного интерфейса. Если у нас есть виртуальный метод в базовом классе, а производный класс снова помечен как виртуальный, в этом случае компилятор дает предупреждение о том, что вы скрываете метод базового класса, поэтому используйте новое, если вы намеренно скрывая метод базового класса.
public interface ITestInterface
{
void virtualmethod(); // this method is by default virtual.
}
public class TestInterface :ITestInterface
{
public virtual void virtualmethod()
{
// Now compiler should consider that i am actually hiding the interface virtual method.
}
}
если вы создадите вышеприведенный код для интерфейса и откройте в ILDASM, вы увидите такой код:
.method public hidebysig newslot abstract virtual
instance void virtualmethod() cil managed
{
}//end of method ITestInterface::virtualmethod
Ответы
Ответ 1
Методы, реализованные с помощью интерфейса, по умолчанию не являются виртуальными. Вы просто предоставляете реализацию контракта, определенного в определении интерфейса. Пометив метод как virtual
, вы позволяете производным классам предоставлять дополнительную или отдельную реализацию, все еще соблюдая контракт как определенный.
Рассмотрим следующий пример:
interface IAnimal
{
string Speak();
}
class Dog : IAnimal
{
public string Speak()
{
return "Bark!";
}
}
Класс Dog
реализует интерфейс, предоставляя реализацию контракта IAnimal
. Здесь нет никаких виртуальных методов и нет переопределения.
Теперь рассмотрим этот пример:
interface IAnimal
{
string Speak();
}
class Dog : IAnimal
{
public virtual string Speak()
{
return "Bark!";
}
}
class GoldenRetriever : Dog
{
public override string Speak()
{
return "I am a golden retriever who says "
+ base.Speak();
}
}
Теперь класс Dog
объявил Speak
равным virtual
, который позволяет производным классам предоставлять дополнительную или новую реализацию. Это не нарушает контракт с IAnimal
, поскольку любой вызов метода Speak
все равно возвращает строку.
Хорошо, последний пример. Помните, что интерфейсы не требуют реализации - они требуют только того, чтобы контракт был выполнен. Это означает, что интерфейс только заботится о том, что член существует в классе реализации, который имеет соответствующую подпись. Это означает, что мы могли бы также сделать это:
interface IAnimal
{
string Speak();
}
abstract class Dog : IAnimal
{
public abstract string Speak();
}
class GoldenRetriever : Dog
{
public override string Speak()
{
return "I am a golden retriever";
}
}
Обратите внимание, что класс Dog
не выполняет никакой реализации для Speak
, но удовлетворяет требованиям контракта.
Интерфейсы также наследуются от класса к классу, поэтому во всех приведенных выше примерах Dog
и GoldenRetriever
реализует интерфейс IAnimal
. Ни один из классов не скрывает метод Speak
- оба класса реализуют его.
Хорошо, я думаю, что ваша путаница может исходить из того, что виртуальный метод определен в интерфейсе, а не в классе. Вот IL для интерфейса I, определенного выше:
.class private interface abstract auto ansi IAnimal
{
.method public hidebysig newslot abstract
virtual instance string Speak() cil managed
{
}
}
В то время как вы правы, что метод определен как virtual
, вам также необходимо заметить, что тип здесь обозначается как interface
. Это просто деталь реализации MSIL, сгенерированный компилятором Microsoft С#, - другой компилятор может легко генерировать другой код, если семантически он дает тот же результат.
Важно следующее: даже если метод объявлен как virtual
в интерфейсе, это не значит, что это то же самое, что и метод virtual
, объявленный в классе.
Ответ 2
Интерфейс не является базовым классом, поэтому методы реализации не переоцениваются. Интерфейс только объявляет методы, интерфейсные методы по умолчанию не являются виртуальными, интерфейсы infact объявляют только те методы, которые доступны в классе, реализующем этот интерфейс.
Объявление не может быть виртуальным.
Реализация может или не может быть виртуальной, которая полностью зависит от логики реализации.
Ответ 3
Здесь происходит перепутывание термина "виртуальный" между IL и С#.
Они не полностью соответствуют. Виртуальный в смысле IL означает "косвенный" через VMT (таблицу виртуальных методов), то есть примерно тот же механизм переопределения классов и реализации интерфейса.
В смысле IL член интерфейса должен быть помечен как виртуальный - пути назад нет.
Но если вы посмотрите на реализацию, она помечена как "виртуальный финал". Это то, чего вы не можете достичь в С#. "Окончательный" означает, что он не может быть переопределен. Он не становится виртуальным в смысле С#, если вы не объявите его вручную как "виртуальный" или "абстрактный" в С#.
Неявная реализация интерфейса С# (она не существует в VB.NET или IL) достаточно мощная. Он присоединяет метод реализующего класса, который соответствует в Name-Parameters-ReturnValue (Signature или SigAndName в формулировке IL).
Это включает использование реализаций базового класса для методов.
public interface ITest
{
double MethodC(double a, double b, double c, double d);
}
internal class BaseClass
{
public double MethodC(double a, double b, double c, double d)
{
return a+b+c+d;
}
}
internal class OtherClass : BaseClass , ITest
{
}
Это на самом деле работает нормально.
Но С# делает здесь хитрость, потому что вы используете BaseClass.MethodC в качестве реализации интерфейса, он помечен как последний виртуальный в BaseClass.
Да, способ реализации BaseClass зависит от того, как используется BaseClass.
BaseClass.MethodC изменен, потому что он используется для реализации ITest.MethodC в производном классе. Это работает даже над границами файлов и проектов, если исходный код BaseClass находится в том же решении.
Таким образом, результаты компиляции проекта не одинаковы, если вы компилируете их самостоятельно или в большом решении вместе с другими продуктами. Это тихо заметно.
Если исходный код BaseClass недоступен, если вы только что связали его с DLL, тогда С# сгенерирует оболочку для использования реализации BaseClass.
На самом деле это будет сделано:
internal class OtherClass : BaseClass , ITest
{
double ITest.MethodC(double a, double b, double c, double d)
{
return base.MethodC(a, b, c, d)
}
}
Это делается секретно, но абсолютно необходимо иметь метод, помеченный как виртуальный в смысле IL.