Почему возможно реализовать метод интерфейса в базовом классе?
В моем проекте я нашел странную ситуацию, которая кажется полностью действительной в С#, потому что у меня нет ошибок времени компиляции.
Упрощенный пример выглядит следующим образом:
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
существует.
Для меня такая конструкция опасна. Поскольку каждый раз, когда я реализую некоторый интерфейс, я должен проверить, не мешает ли каждый метод в этом интерфейсе методам базового класса.
Если это "чистая функция С#"? Как это называется? Я что-то пропустил?
Ответы
Ответ 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 может ввести новый метод и написать собственную реализацию.