Бывает ли ситуация, когда производный класс должен скрываться…?

Возможно, глупый вопрос, но если предположить, что 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");
    }
}

Спасибо


person flockofcode    schedule 13.07.2010    source источник
comment
Привет, как уже видно из вопросов, образец касается виртуальных функций (которые являются фундаментальными для полиморфизма), где вопрос, похоже, касается сокрытия невиртуальных членов базового класса. Что он?   -  person    schedule 13.07.2010
comment
jdv: Я не вижу в этом вопросе ничего о сокрытии невиртуальных членов базы.   -  person dthorpe    schedule 14.07.2010


Ответы (5)


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

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

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

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

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

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

person dthorpe    schedule 13.07.2010
comment
Насколько я понимаю, у методов расширения есть более серьезные проблемы с подобными вещами. Если Боб создает метод расширения .Print, а затем базовый класс добавляет метод, который работает иначе, все будет работать нормально // до тех пор, пока код не будет перекомпилирован //. - person supercat; 13.07.2010
comment
Как все нормально работает до перекомпиляции кода? Изменения не применялись до его перекомпиляции. Я не припомню, является ли наличие метода расширения, скрывающего одноименный метод с одинаковой сигнатурой в классе, ошибкой компилятора в C #. - person dthorpe; 14.07.2010
comment
Языки поддерживают виртуальное сокрытие, чтобы сделать объектные структуры более устойчивыми к будущим изменениям. Это основная причина того, что C # поддерживает виртуальное сокрытие? - person flockofcode; 14.07.2010
comment
@flockofcode: Да. Это было причиной того, что виртуальное сокрытие было реализовано в языке Delphi в середине 90-х годов. Я не участвовал в разработке C #, но я бы сказал, что было бы разумным предположить, что C # поддерживает виртуальное сокрытие по тем же причинам, что и Delphi. В конце концов, языки C # и Delphi были разработаны одним и тем же архитектором - Андерсом Хейлсбергом. - person dthorpe; 15.07.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 никуда не годятся, поэтому все их обычные магические свойства полиморфизма по-прежнему применяются.

person Brian Gideon    schedule 13.07.2010

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

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

person spinon    schedule 13.07.2010
comment
фууу. Если вы делаете такой призыв, то я могу возразить, что использование этого API в первую очередь было плохим вызовом. Надеюсь, для этого есть серьезная деловая причина. - person Greg D; 13.07.2010
comment
@Greg Да, я согласен. Это было деловое решение до того, как я туда попал, и им понравился фреймворк. Поэтому, чтобы все не пропустить под палящим солнцем, мы спрятали вещи. - person spinon; 13.07.2010

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

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

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

person Community    schedule 13.07.2010
comment
Я как раз думал об этом (свойство «только для чтения» становится «чтение-запись»). Есть ли способ в vb.net (pref. 2005), чтобы класс одновременно затенял и переопределял функцию? Например, если базовый класс определяет абстрактное свойство, доступное только для чтения, может ли подкласс переопределить это свойство только для чтения и создать новое переопределяемое свойство для чтения и записи с тем же именем? - person supercat; 13.07.2010
comment
@supercat: я не это имел в виду, хотя вижу, что это полезно. С помощью интерфейсов вы можете объявить только для чтения и сделать его доступным для чтения и записи в реализации. Если вы сделаете явную реализацию i / f InterfaceName.Property, вы получите еще больше свободы. (но будьте осторожны: легко стать слишком креативным ...) - person ; 13.07.2010

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

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

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

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

person Tesserex    schedule 13.07.2010
comment
Скрытие виртуального метода семантически не идентично переопределению из-за отсутствия виртуальной отправки, когда метод вызывается из ссылки на базовый класс. - person Greg D; 13.07.2010