У меня проблема с моим кодом.
Я ожидаю, что, поскольку я создаю объект реализации; каждый раз, когда я вызываю Method(), я бы использовал фактическую реализацию.Method(), а не абстрактный Base.Method(). Не кажется разумным, что я должен приводить к фактическому реализатору или явно указывать интерфейс (значит, интерфейсы не являются транзитивными в С#? Я буду называть «первый правильный экземпляр реализатора интерфейса», а не мой класс?)
У меня есть структура, похожая на эту (упрощенную для ясности): https://dotnetfiddle.net/oYVlQO
using System;
public interface IBase
{
string Method();
}
public abstract class Base : IBase
{
public string Method() { return "Sample"; }
}
public class Implementation : Base // if I add ", IBase" to this it works as expected, but why?
{
new public string Method() { return "Overriden"; }
}
public class Program
{
// and it's used like so...
public static void Main()
{
IBase b = new Implementation();
//Implementation b = new Implementation(); // It works as expected, always using Implementation.Method();
Console.WriteLine(b.Method()); // Produces "Sample", so Base.Method(). Why not implementation?
Console.WriteLine(((Implementation) b).Method()); // Produces "Overriden", so Implementation.Method(); Since when I have to downcast to use overriden method!?
}
}
}
Я действительно ломаю голову над этим; Тем более, что тот же код на Java работает "как я и ожидал" https://repl.it/repls/VitalSpiritedВеб-страница
Я пытался найти его в спецификациях С# безрезультатно, возможно, у меня нет подходящих ключевых слов...
@ Cᴏʀʏ Это работает, спасибо. Я так предполагаю, что ситуация возникает из-за этого? «Когда вызывается виртуальный метод, тип объекта во время выполнения проверяется на наличие переопределяющего члена. Вызывается переопределяющий член в наиболее производном классе, который может быть исходным членом, если ни один производный класс не переопределял член. " Итак, как мне подойти к коду, в котором (потребляемый) абстрактный класс имеет метод, НЕ помеченный как виртуальный, как я могу их переопределить? Только через понижение?
Использование модификатора new является НЕТ некоторой формой переопределения. Использование (необязательного) модификатора new означает скрытие членов (также называемое затенением членов). Переопределение и сокрытие — две вещи совершенно другой!
"Тем более что тот же код на Java работает "как я и ожидал"" Только если вы относитесь к C# просто как к диалекту Java... (*кашляет*) ;-P
Если член не помечен virtual или abstract, он не может быть отвергнутый, потому что тип объекта во время выполнения не проверяется при его вызове (отсутствует «динамическая диспетчеризация»). Реализация для вызова определяется только объявленным типом ссылки. В java члены по умолчанию виртуальные, а в C# — нет.
Спасибо всем :) Ну, это показывает, что я пишу в основном на Java. После некоторого чтения (stackoverflow.com/questions/1853896/…) я теперь могу понять, по крайней мере, почему он ведет себя именно так и как правильно обойти эту проблему (через композицию).
Вы добавили ключевое слово new, когда компилятор сказал вам, что вы сделали это неправильно. Это предупреждение точно в 99% случаев, вы должны быть уверены. После того, как вы это сделали, у класса есть методы два с именем Method(), унаследованный метод скрыт, и к нему трудно получить доступ без бэкдора, который предоставляется интерфейсом. С тем же успехом вы могли бы назвать его OtherMethod() и не получить предупреждения. Обратите внимание, что C# отличается от Java, методы являются виртуальными только тогда, когда вы объявляете их таким образом. В Java все методы виртуальные.





В связи с вопросом, который заключается в следующем: Почему это так? Мой ответ: Потому что вы не переопределяете метод, а скрываете его. Интерфейс реализуется базовым классом, поэтому метод вызывается в базовом классе. Чтобы ответить на вопрос, который не задан: Как это будет работать?
Отвечать:
using System;
public interface IBase
{
string Method();
}
public abstract class Base : IBase
{
public virtual string Method() { return "Sample"; }
}
public class Implementation : Base
{
public override string Method() { return "Overriden"; }
}
Что ж, вы адаптировали код из вопроса, но оставили нетронутыми комментарии, относящиеся к исходному коду. Для некоторых поведение вашего кода по сравнению с комментариями в вашем коде может сделать ваш пример кода здесь несколько запутанным...
@elgonzo Спасибо, что указали на это. Я всегда использую исходный код, чтобы максимально соответствовать потребностям ОП, но комментарии следовало удалить. Обновлено.
Вы можете взглянуть на ту часть спецификации C#, которая касается повторная реализация интерфейса..
Когда вы получаете доступ к члену через интерфейс, он начинает поиск с наиболее производного типа, который явно реализует этот интерфейс. В вашем примере наиболее производным типом является Base, поэтому он вызывает метод, который там присутствует.
Когда вы добавили IBase в список интерфейсов, явно реализованных Implementation, это сработало, потому что это новая отправная точка для поиска, и он находит ваш новый метод.
Вы можете либо решить свою проблему, сделав базовый элемент виртуальным, а затем переопределив его в производных классах, либо повторно реализовать интерфейс, включив его в список для вашего класса Implementation.
Повторная реализация интерфейса не относится к рассматриваемой здесь проблеме (поскольку для этого потребуется, чтобы класс Implementation включил интерфейс IBase в свой список базовых классов, чего он не делает).
@elgonzo Вы правы, я сделал это поспешно. Я, вероятно, напишу свою собственную запись на основе ваших ответов, спасибо
@elgonzo Вы можете взглянуть на комментарий рядом с типом Implementation в вопросе @Jacek. Это явный результат повторной реализации интерфейса и отвечает на вопрос.
Ну, в самом деле, держись. Я только сейчас заметил вопрос в комментарии относительно IBase для реализации. Так что, думаю, оставь свой ответ. Возможно, просто уточните, что вы ссылаетесь на «вопрос о комментарии к коду», который можно легко пропустить...
@ChrisHannon, да, я написал свой последний комментарий, пока вы писали свой комментарий ;-)
Таким образом, проблема в моем примере кода двоякая.
Я предположил, что в С# методы по умолчанию являются «виртуальными» (как и в Java). Поскольку я обычно пишу код на Java, я сделал неверное предположение.
См. Можно ли переопределить не виртуальный метод?
Если бы я использовал виртуальный, я мог бы переопределить метод и получить именно тот результат, который ожидал, как описано в документе:
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/virtual, а именно: «Когда вызывается виртуальный метод, тип объекта во время выполнения проверяется на наличие переопределяющего члена. Вызывается переопределяющий член в самом производном классе, который может быть исходным членом, если ни один производный класс не переопределял член."
Мой код, однако, использует скрытие метода, поэтому, если я не сообщу компилятору о своем намерении использовать мою реализацию, по умолчанию он будет использовать нескрытый метод (как разрешено абстрактным классом, являющимся фактическим исходным реализатором)
Method()в вашем абстрактномBaseклассе следует пометитьvirtual, а затем вместо использованияnew public methodв вашемImplementationсделайтеpublic override string Method().newпредоставляет новую реализацию метода, скрывая его в базовом классе.virtual+overrideфактически переопределяет метод.