Подтвердить что ты не робот

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

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

Упрощенный пример выглядит следующим образом:

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 даже не знает, что IFoo существует.

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

Если у вас есть это:

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

}

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

Ответ 3

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

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

Ну, неважно, что думал автор A во время создания A. Это автор B, который должен взять на себя ответственность за то, что B наследует от A И реализует IFoo. Автор B должен думать о последствиях определения B.

Вы также говорите

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

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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

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

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

interface IFoo2
{
    void FooMethod();
}

class B : A, IFoo, IFoo2
{
}

Ответ 7

Раздел 13.4.4. спецификации С#:

Отображение интерфейса для класса или структуры C находит реализацию для каждого члена каждого интерфейса, указанного в списке базового класса C. Реализация конкретного члена интерфейса IM, где я является интерфейсом, в котором объявлен член M, определяется путем изучения каждого класса или структуры S, , начиная с C и повторяя для каждого последующего базового класса 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.

Ответ 8

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

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

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

Ответ 9

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

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

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

IFoo.Method(int a, int b, int c) {IFoo_Method (a, b, c); }

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

Ответ 10

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

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

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