Почему возможно реализовать метод интерфейса в базовом классе? [дубликат]


этот вопрос уже есть ответ здесь:

  • Почему базовый класс В C# может реализовать контракт интерфейса без наследования от него? 2 ответы

в моем проекте я нашел странную ситуацию, которая кажется полностью допустимой в C#, потому что у меня нет ошибок компиляции.

упрощенный выглядит так:
using System;
using System.Collections.Generic;

namespace Test
{

    interface IFoo
    {
        void FooMethod();
    }

    class A
    {
        public void FooMethod()
        {
            Console.WriteLine("implementation");
        }
    }

    class B : A, IFoo
    {
    }

    class Program
    {
        static void Main(string[] args)
        {
            IFoo foo = new B();
            foo.FooMethod();
        }
    }
}

такой код компилируется. Однако, обратите внимание, что A не IFoo и B не выполнять IFoo методы. В моем случае, случайно (после рефакторинга), A имеет метод с той же сигнатурой. Но с какой стати A знать, как реализовать FooMethod на IFoo интерфейс? A даже не знаю, что .

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

если это " чистая функция C#"? Как это называется? Я что-то упустил?

10 59

10 ответов:

для каждого члена интерфейса компилятор просто ищет явно реализация (если один), то общественные реализация (подразумевается реализация), т. е. метод на публичном API, который соответствует сигнатуре интерфейса. В этом случае A.FooMethod() выглядит как прекрасное соответствие для публичной реализации. Если B не был доволен этим выбором, он мог либо new метод, или использовать явную реализацию; последний будет предпочтительно:

void IFoo.FooMethod() { /* explicit implementation */ }

ключевое слово implements. Ваш базовый класс, хотя он ничего не знает о IFoo была объявлена сигнатура метода, которая реализует метод в вашем интерфейсе где-то в вашей иерархии классов.

поэтому, когда вы реализуете IFoo в производном классе он уже имеет сигнатуру метода, реализованную в структуре класса, поэтому ее не нужно реализовывать снова.

если бы у вас было это:

interface IFoo
{
  void FooMethod();
}
class A
{
  private void FooMethod(){}
}
class B : A, IFoo
{

}

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

вы говорите в комментарии

насколько вероятно, что тот, кто написал реализацию FooMethod на A класс, который не реализует IFoo на самом деле предназначены для реализации IFoo?

Ну, это не имеет значения, что писатель A думал во время A'Ми. Это автор B кто должен нести ответственность за то, что B как наследует от A и осуществляет IFoo. это автор B подумать о последствиях определения B.

Вы тоже скажете

в моем случае случайно (после рефакторинга) A имеет метод с той же сигнатурой

предполагая, что эта ситуация возникла после A и B были написаны. В этом случае ситуация меняется: при редактировании класса, который * наследуется от * (например,A),это ответственность редактора за проверку влияния редактирования на все наследующие классы.

чтобы реализовать интерфейс, класс должен только (a) объявить, что он реализует этот интерфейс (например, ваш класс B), и (b) предоставить реализации для всех методов, определенных в интерфейсе, прямо или косвенно через базовый класс (например, ваш класс B).

13.4.4. в C# спецификации:

отображение интерфейса для класса или структуры C находит реализацию для каждого элемента каждого интерфейса, указанного в списке базовых классов C. реализация конкретного элемента интерфейса I. M, где I-интерфейс, в котором объявлен элемент M, определяется путем изучения каждого класса или структуры S,начиная с C и повторяя для каждого последующего базового класса С, пока не будет местонахождение:

так что кажется, что это хорошо определенное поведение, как правильная реализация FooMethod не найден в B, поэтому поиск выполняется по его базовому классу A где найден метод с соответствующей сигнатурой. Это даже явно указано в том же разделе спецификации:

члены базового класса участвуют в отображении интерфейса. В Примере

interface Interface1
{
void F();
}
class Class1
{
    public void F() {}
    public void G() {}
}
class Class2: Class1, Interface1
{
    new public void G() {}
}

метод F в Class1 используется в реализации Class2 Interface1.

функция называется наследование. И если вам не нравится дизайн, просто не используйте его. Многие люди не любят наследование, так что Вы тоже можете. Определение наследования состоит в том, что все члены базового класса также являются членами производного класса. Так что нет никакой ошибки компилятора. Поэтому производное реализует контракт IFoo обеспечивает. Это член базового класса, который выполняет это требование.

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

интерфейсы не наследуются, интерфейсы реализованы. Таким образом, когда вы производите класс от интерфейса, это означает

Эй интерфейс, вы найдете здесь метод, который реализует метод подпись Вы предоставили.

поскольку базовый класс имеет реализацию метода, с тем же методом signiture, определенным в интерфейсе, проблем не будет.

даже если вы пишете второй интерфейс с включением же метод подписи, он все равно будет работать.

interface IFoo2
{
    void FooMethod();
}

class B : A, IFoo, IFoo2
{
}

" но почему A должен знать, как реализовать FooMethod интерфейса IFoo? А даже не знает, что IFoo существует."

A не нужно знать о существовании интерфейса IFoo. Это не обязанность A правильно реализовать FooMethod. По-видимому, случилось реализовать метод, который имеет такую же подпись, что и метод интерфейса IFoo FooMethod.

его ответственность B реализовать FooMethod, так как он реализует интерфейс IFoo. Но так как Б уже имея метод с именем FooMethod (наследуется от A), ему не нужно реализовывать его явно. Если унаследованный метод не выполняет свою работу, B может создать новый метод и написать свою собственную реализацию.

B реализовать IFOO. B наследует от A так это на самом деле выглядит так:

class B : IFoo  //Notice there is no A here. 
{
    public void FooMethod()
    {
        Console.WriteLine("implementation");
    }
}

и это ясно (из приведенного выше кода), что B осуществляет IFoo и ничего особенного.

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

это было сказано, Я думаю, что для C# было бы чище решить эту общую проблему (методы интерфейса, которые цепляются к другим членам, уродливы), предоставив синтаксис для явного присоединения элемента интерфейса к члену класса, чем использовать семантику автоматической привязки для обработки одной конкретной ситуации, но требуют цепочки в более общей ситуации (реализация интерфейса защищенным виртуальным методом):

защищенный виртуальный IFoo_Method(int a, int b, int с) { ... }

IFoo.Метод(int a, int b, int c) { IFoo_Method(a,b, c);}

в то время как дрожание может быть в состоянии выяснить, что вызов IFoo_Method должен быть встроен, это действительно не должно быть. Казалось бы чище объявить, что защищенный метод IFoo_Method следует рассматривать как реализацию IFoo.Method.