Есть ли когда-нибудь ситуация, когда производный класс должен скрываться ...? - PullRequest
4 голосов
/ 13 июля 2010

Возможно, глупый вопрос, но при условии, что base class A определяет virtual method V, существует ли ситуация, когда derived class C имеет смысл скрыть A.V, объявив новый virtual method C.V с той же подписьюкак A.V:

class Program
{
    static void Main(string[] args)
    {
        A a = new C();
        a.Print(); // prints "this is in B class"

        C c = new C();
        c.Print();// prints "this is in C class"
    }
}

class A
{
    public virtual void Print()
    {
        Console.WriteLine("this is in A class");
    }
}

class B:A
{
    public override void Print()
    {
        Console.WriteLine("this is in B class");
    }
}

class C : B
{
    public virtual void Print()
    {
        Console.WriteLine("this is in C class");
    }
}

Спасибо

Ответы [ 5 ]

8 голосов
/ 13 июля 2010

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

Пример: Релиз 1 структуры объектов X не предоставляет функцию Print ().Боб решает расширить несколько объектов платформы X, определив функции Print () в своих собственных классах-потомках.Так как он планирует переопределить их в более специфических классах, он также делает функцию Print () виртуальной.

Позже, Выпуск 2 объектной структуры X выпущен.Боб решает обновить свой текущий проект, чтобы использовать Выпуск 2 вместо Выпуска 1. Без ведома Боба команда объектной инфраструктуры X также решила, что Print () будет полезной функцией, и поэтому они добавили виртуальный Print () в один избазовые классы фреймворка.

При виртуальном скрытии классы-потомки Боба, содержащие реализацию Print () Боба, должны компилироваться и нормально работать, даже если теперь в базовых классах существует другой Print () - даже с другой сигнатурой метода.Код, который знает о базовом классе Print (), продолжит его использовать, а код, который знает о Bob's Print (), продолжит его использовать.Никогда эти два не встретятся.

Без виртуального сокрытия код Боба не будет компилироваться вообще, пока он не выполнит некоторую нетривиальную операцию со своим исходным кодом, чтобы устранить конфликт имен в Print ().Некоторые утверждают, что это «правильная» вещь (отказ от компиляции), но реально любая ревизия базовой библиотеки, которая требует пересмотра существующего рабочего кода, не будет удовлетворена потребителями.Они будут обвинять фреймворк в том, что он сломал все, и плохо о нем говорят.

Было бы разумно, чтобы Боб получил предупреждение компилятора о том, что базовая печать скрыта печатью Боба, но это не фатальная ошибка.Это то, что Боб, вероятно, должен очистить (переименовав или удалив свою функцию Print ()) как можно скорее, чтобы избежать путаницы.

2 голосов
/ 13 июля 2010

Я видел, что он использовался в сценариях, где вы хотите автоматически привести член базового класса к более узкому типу из подкласса.

public interface IFoo { }

public class ConcreteFoo : IFoo { }

public abstract class Bar
{
    private IFoo m_Foo;

    public IFoo Foo
    {
        get { return m_Foo; }
    }

    protected void SetFoo(IFoo foo)
    {
        m_Foo = foo;
    }
}

public class ConcreteBar : Bar
{
    public ConcreteA(ConcreteFoo foo)
    {
        SetFoo(foo);
    }

    public new ConcreteFoo Foo
    {
        get { return (ConcreteFoo)base.Foo; }
    } 
}

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

1 голос
/ 13 июля 2010

Чтобы присоединиться к хору, я согласен, что это плохая идея.Большинство примеров, которые я видел, показывают, что разработчик хотел бы, чтобы метод базового класса был виртуальным, но, к сожалению, это не так.Затем вы объявляете его новым и надеетесь, что никто не вызывает этот метод через указатель базового класса на экземпляр вашего объекта.Возможность взять метод virtual и переопределить его как new virtual, это категория премии Дарвина.

Кроме разработчиков, new также сбивает с толку инструменты запутывания.Так что имейте в виду.

Тем не менее, я недавно нашел одно полезное приложение для члена new: я использовал его для повторного объявления открытого только для чтения свойства типа интерфейса ISomthing как свойства, котороевернул фактический тип ConcreteSomething.Оба возвращали одну и ту же ссылку на объект, за исключением того, что в последнем случае объект возвращается как сам по себе, а не как указатель на интерфейс.Это спасло много много ударов.

1 голос
/ 13 июля 2010

Имеет ли это смысл, все зависит от класса.Если функциональность базового класса недоступна для пользователей вашего класса, вам следует скрыть это, чтобы они не допустили ошибок в этом.Хотя обычно это происходит, когда вы используете API, над которым у вас нет слишком большого контроля.Если вы пишете что-то, то, вероятно, лучше просто вернуться и изменить доступ к методам.

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

0 голосов
/ 13 июля 2010

Ну, во-первых, если вы действительно хотите скрыть, то вам нужно ключевое слово new. Вы не хотите просто вставить туда virtual.

class C : B
{
    public new void Print()
    {
        Console.WriteLine("this is in C class");
    }
}

Но поскольку метод изначально был виртуальным, вы ничего не скрываете. Ожидается, что ваши производные классы переопределят метод и придадут ему уникальное поведение. Однако, если метод не был сделан виртуальным, но вам действительно нужно его изменить, вы можете использовать ключевое слово new. Обратите внимание, что вы не можете полностью скрыть метод, переопределив его как частный. Если вы попробуете, он просто вызовет публичную реализацию в базовом классе. Лучшее, что вы можете сделать, это переопределить его, чтобы бросить NotSupportedException.

А что касается того, почему вы это сделаете: если бы вы сами написали класс А, я бы сказал, что у вас нет настоящей причины. Но если вы этого не сделали, у вас может быть веская причина, по которой вы не хотите, чтобы этот метод вызывался. В одном проекте, над которым я работаю, у меня есть класс, производный от List<>, но я не хочу вызывать Add, я требую, чтобы был вызван специальный метод, чтобы я мог лучше контролировать добавляемые элементы. В этом случае я скрыл new и заставил его выбросить NotSupportedException с сообщением для использования другого метода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...