Почему виртуальный разрешен при реализации методов интерфейса?

У меня есть один конкретный запрос с интерфейсами. По умолчанию методы интерфейса абстрактны и виртуальны, поэтому, если мы реализуем этот интерфейс и предоставляем определение в классе, мы фактически переопределяем этот метод, но когда мы снова помещаем этот метод как виртуальный в классе реализации, почему компилятор не считает, что мы действительно пытаемся чтобы скрыть виртуальный метод исходного интерфейса. Если у нас есть виртуальный метод в базовом классе, а производный класс снова помечен как виртуальный, в этом случае компилятор дает предупреждение о том, что вы скрываете метод базового класса, поэтому используйте новое, если вы намеренно скрывая метод базового класса.

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.