Вызов виртуального метода класса из невиртуального
От: Pek2014 Россия  
Дата: 14.05.15 13:19
Оценка:
Разбираюсь, с темой "почему в конструкторе не надо вызывать виртуальные
методы?" Разобрался, понял, но...

... то, что я понял, подталкивает меня к ещё более категорическому правилу:
"Не вызывайте виртуальные методы класса из его невиртуальных методов".

Как вам такая рекомендация? Может она очевидна, а может, наоборот, это
глупость?

Вот примерные рассуждения:

Что значит, что метод класса виртуальный (абстрактный)? Это значит, что
наследники класса могут поменять поведение этого метода. Так?

Что значит, что метод класса невиртуальный? Значит ли это, что наследники класса
не могут поменять поведение этого метода? Значит ли это, этот метод ведёт себя
одинаково для любых наследников класса. Может ли клиентский класс на это
рассчитывать?

Нет. Если в реализации невиртуального метода вызывается виртуальный метод, то
наследник всё-таки сможет изменить поведение метода (пусть не явно, не
переопределяя этот метод целиком, а лишь переопределяя вызываемые из метода
виртуальные методы).

Значит гарантировать "строгую невиртуальность" метода можно только используя
определённую дисциплину кодирования?

Зачем нужна "строгая невиртуальность"?
Например клиентский код хочет реализовать строго детерминированный алгоритм
(одинаковые данные на вход, должны давать одинаковые данные на выходе, вне
зависимости от того с каким наследником базового класса имеем дело).
Или мы хотим гарантированно обезопасить себя от побочных эффектов использования
кода наследника базового класса...

Короче, хочется, гарантий чтобы работал только код базового класса, несмотря,
на то что объект — это наследник.

Хочется странного?
Re: Вызов виртуального метода класса из невиртуального
От: tyomchick Россия  
Дата: 14.05.15 13:23
Оценка: +1
Здравствуйте, Pek2014, Вы писали:

P>Разбираюсь, с темой "почему в конструкторе не надо вызывать виртуальные

P>методы?" Разобрался, понял, но...

P>... то, что я понял, подталкивает меня к ещё более категорическому правилу:

P>"Не вызывайте виртуальные методы класса из его невиртуальных методов".

А откуда же их вызывать?
Даже самую простую задачу можно сделать невыполнимой, если провести достаточное количество совещаний
Re: NVI
От: Qbit86 Кипр
Дата: 14.05.15 13:34
Оценка: 1 (1) +1
Здравствуйте, Pek2014, Вы писали:

P>"Не вызывайте виртуальные методы класса из его невиртуальных методов".


P>Как вам такая рекомендация? Может она очевидна, а может, наоборот, это

P>глупость?

Есть идиома NVI (Non-Virtual Interface), согласно которой виртуальные методы класса желательно вызывать только из его же невиртуальных методов. Обоснование можно почитать в книжке Саттера-Александреску.
Глаза у меня добрые, но рубашка — смирительная!
Re[2]: NVI
От: Pek2014 Россия  
Дата: 14.05.15 13:48
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, Pek2014, Вы писали:


P>>"Не вызывайте виртуальные методы класса из его невиртуальных методов".


Q>Есть идиома NVI (Non-Virtual Interface), согласно которой виртуальные методы класса желательно вызывать только из его же невиртуальных методов. Обоснование можно почитать в книжке Саттера-Александреску.


Это понятно. Понятно, что это рекомендация не на все случаи в жизни.

NVI кстати тоже порождена озабоченностью тем, что там напереопределяли наследники.
Эта идиома позволяет оставлять контроль в руках базового класса.
Входы и выходы проходят через код базового класса.

Я озабочен похожими вещами. Хочу быть уверен, что выполняется только код базового класса.
Например, разработчику базового класса я доверяю, а вот разработчикам наследников — нет (точнее — не всегда).
Re[2]: Вызов виртуального метода класса из невиртуального
От: Pek2014 Россия  
Дата: 14.05.15 13:56
Оценка:
Здравствуйте, tyomchick, Вы писали:

T>Здравствуйте, Pek2014, Вы писали:


P>>Разбираюсь, с темой "почему в конструкторе не надо вызывать виртуальные

P>>методы?" Разобрался, понял, но...

P>>... то, что я понял, подталкивает меня к ещё более категорическому правилу:

P>>"Не вызывайте виртуальные методы класса из его невиртуальных методов".

T>А откуда же их вызывать?


Во-первых их можно и нужно вызывать из других виртуальных методов
А главное из можно вызывать из клиентского кода (кода, который использует класс).

Клиентский код понимает, что вызывает виртуальный метод,
т.е. метод, поведение которого зависит от настоящего типа вызываемого объекта.
Он предупреждён, что поведение может отличаться от специфицированного
в базовом классе.

Когда он вызывает невиртуальный метод, он может (ошибочно) рассчитывать
на детерминированность результата, которую (детерминированность) мы ему
можем гарантировать, только если невиртуальный метод базового класса
не будет вызывать виртуальные методы.
Re: Вызов виртуального метода класса из невиртуального
От: Sinix  
Дата: 14.05.15 13:59
Оценка:
Здравствуйте, Pek2014, Вы писали:

P>Как вам такая рекомендация? Может она очевидна, а может, наоборот, это

P>глупость?

Ну.. это не глупость, это попытка поддержать любой унаследованный код, даже нарушающий официальные design guidelines и LSP.

С конструкторами всё просто: в шарпе нет универсального способа правильно проинициализировать поля до вызова базового конструктора.

Не, есть
class A: SomeType
{
  int x = 0; // before
  int y;

  A(int abc): base(abc) // base .ctor calls SomeAction()
  {
    y = abc; // after
  }

  public override void SomeAction()
  {
    Write(x); // (1), ok
    Write(y); // (2), oops
  }
}


Но и тут есть подвох с строчкой (2). Т.е.
* или автор SomeAction() как-то гарантирует, что его метод (и вся цепочка вызовов) никак не зависит от полей класса (ну и что тогда метод делает в этом классе?)
* или автор закладывается на "никто не вызовет мой метод до инициализации полей, кто вызвал — сам себе злой буратина"

В большинстве случаев вариант 2 реализовать значительно проще, поэтому и пришли к рекомендации "не вызывать виртуальные методы из конструктора". Надо помнить, что это не запрет, а рекомендация, т.е. при наличии веских причин и понимания, что ты делаешь, можно и нарушить. Оф. документация:

X AVOID calling virtual members on an object inside its constructor.

Calling a virtual member will cause the most derived override to be called, even if the constructor of the most derived type has not been fully run yet.




P>Что значит, что метод класса виртуальный (абстрактный)? Это значит, что

P>наследники класса могут поменять поведение этого метода. Так?
Да, но общепринятым правилом хорошего тона является соблюдение LSP. Т.е. наследник должен соблюдать контракт базового класса (разумеется, если этот контракт как-то документирван).


P>Значит гарантировать "строгую невиртуальность" метода можно только используя

P>определённую дисциплину кодирования?
С этой проблемой борются слегка другим способом. Опять-таки, официальные guidelines:

Virtual members, like callbacks (and maybe more than callbacks), are costly to design, test, and maintain because any call to a virtual member can be overridden in unpredictable ways and can execute arbitrary code. Also, much more effort is usually required to clearly define the contract of virtual members, so the cost of designing and documenting them is higher.

X DO NOT make members virtual unless you have a good reason to do so and you are aware of all the costs related to designing, testing, and maintaining virtual members.

Virtual members are less forgiving in terms of changes that can be made to them without breaking compatibility. Also, they are slower than non-virtual members, mostly because calls to virtual members are not inlined.

√ CONSIDER limiting extensibility to only what is absolutely necessary.

И virtual не запрещён, и код не поломан


P>Короче, хочется, гарантий чтобы работал только код базового класса, несмотря,

P>на то что объект — это наследник.
Ну так документируем контракт ассертами/тестами и вперёд. Это и для кода без наследников неплохо бы сделать, особенно с учётом

клиентский код хочет реализовать строго детерминированный алгоритм
(одинаковые данные на вход, должны давать одинаковые данные на выходе, вне
зависимости от того с каким наследником базового класса имеем дело).

Re[3]: Вызов виртуального метода класса из невиртуального
От: tyomchick Россия  
Дата: 14.05.15 14:21
Оценка:
Здравствуйте, Pek2014, Вы писали:

T>>А откуда же их вызывать?


P>Во-первых их можно и нужно вызывать из других виртуальных методов

P>А главное из можно вызывать из клиентского кода (кода, который использует класс).

Другими словами стек вызова виртуальных методов может идти только из вызова клиентского виртуального метода? Кто то должен первым вызвать первый виртуальный метод
Мой опыт говорит мне о том, что так мы теряем процентов 80 смысла в виртуальных методах.


P>Клиентский код понимает, что вызывает виртуальный метод,

P>т.е. метод, поведение которого зависит от настоящего типа вызываемого объекта.
P>Он предупреждён, что поведение может отличаться от специфицированного
P>в базовом классе.

Я не понимаю почему вообще клиентский код должен заморачиваться виртуальностью или не виртуальностью метода? Это внутреннее дело класса. Для клиентского кода есть контракт объекта, виртуальность — это элемент контракта класса для потомков, а никак не для клиентского кода.

P>Когда он вызывает невиртуальный метод, он может (ошибочно) рассчитывать

P>на детерминированность результата

Не должен он ни на что расчитывать. Он должен точно знать, что потомки ему ничего не испортят.
Это ему должен обеспечивать контракт для потомков (public + protected).
Даже самую простую задачу можно сделать невыполнимой, если провести достаточное количество совещаний
Отредактировано 14.05.2015 14:23 tyomchick . Предыдущая версия . Еще …
Отредактировано 14.05.2015 14:22 tyomchick . Предыдущая версия .
Re: Вызов виртуального метода класса из невиртуального
От: vmpire Россия  
Дата: 14.05.15 14:41
Оценка:
Здравствуйте, Pek2014, Вы писали:

P>... то, что я понял, подталкивает меня к ещё более категорическому правилу:

P>"Не вызывайте виртуальные методы класса из его невиртуальных методов".
...
P>Хочется странного?
Я считаю, что да, хочется странного.
Во-первых, если нужно вызывать именно метод базового класса с гарантированным поведением, то тут не нужно делать наследование совсем. И сделать класс sealed, чтобы это было гарантированным.
А если наследники должны часть функциональности всё же переопределять, то имеет прямой смысл разделить класс на два: с фиксированным и с переменным поведением.
Во-вторых, если мы делаем наследование с переопределением части функциональности, то мы накогда не можем в общем случае гарантировать, что наследник что-то не сломает. То есть, да, нужна дисциплина кодирования. Госпожа Лисков для формализации этих требований уже сформулировала принцип, который сейчас считается общепринятым (LSP).
Re[2]: Вызов виртуального метода класса из невиртуального
От: Pek2014 Россия  
Дата: 14.05.15 15:19
Оценка:
Здравствуйте, vmpire, Вы писали:

P>>... то, что я понял, подталкивает меня к ещё более категорическому правилу:

P>>"Не вызывайте виртуальные методы класса из его невиртуальных методов".
V>...
V>Во-вторых, если мы делаем наследование с переопределением части функциональности, то мы накогда не можем в общем случае гарантировать, что наследник что-то не сломает.

Видимо да. Согласен.

Теперь ударюсь в фантазии...

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

Может быть специалисты по безопасности меня поддержат?
Re[3]: Вызов виртуального метода класса из невиртуального
От: vmpire Россия  
Дата: 14.05.15 15:34
Оценка: +1
Здравствуйте, Pek2014, Вы писали:

P>Теперь ударюсь в фантазии...


P>А что, если ввести новое ключевое слово языка, гарантирущее, что невиртуальный метод

P>не вызывает виртуальных методов, т.е. при его вызове выполняется только код базового класса
P>и поэтому наследник никак не может повлиять на поведение этого метода?
Если выделить виртуальные методы в отдельный класс, то ключевое слово уже есть: sealed (в С#) либо final (в Java)
Re[4]: Вызов виртуального метода класса из невиртуального
От: Pek2014 Россия  
Дата: 14.05.15 16:13
Оценка:
Здравствуйте, vmpire, Вы писали:

V>Здравствуйте, Pek2014, Вы писали:


P>>Теперь ударюсь в фантазии...


P>>А что, если ввести новое ключевое слово языка, гарантирущее, что невиртуальный метод

P>>не вызывает виртуальных методов, т.е. при его вызове выполняется только код базового класса
P>>и поэтому наследник никак не может повлиять на поведение этого метода?
V>Если выделить виртуальные методы в отдельный класс, то ключевое слово уже есть: sealed (в С#) либо final (в Java)

Не очень понимаю, чем sealed может помочь...

Хочется иметь особо помеченные методы базового класса,
которые гарантированно (компилятором) не могут изменить своё поведение в производных классах.
Re: Вызов виртуального метода класса из невиртуального
От: Qulac Россия  
Дата: 14.05.15 16:13
Оценка:
Здравствуйте, Pek2014, Вы писали:

P>Разбираюсь, с темой "почему в конструкторе не надо вызывать виртуальные

P>методы?" Разобрался, понял, но...

P>... то, что я понял, подталкивает меня к ещё более категорическому правилу:

P>"Не вызывайте виртуальные методы класса из его невиртуальных методов".

P>Как вам такая рекомендация? Может она очевидна, а может, наоборот, это

P>глупость?

P>Вот примерные рассуждения:


P>Что значит, что метод класса виртуальный (абстрактный)? Это значит, что

P>наследники класса могут поменять поведение этого метода. Так?

P>Что значит, что метод класса невиртуальный? Значит ли это, что наследники класса

P>не могут поменять поведение этого метода? Значит ли это, этот метод ведёт себя
P>одинаково для любых наследников класса. Может ли клиентский класс на это
P>рассчитывать?

P>Нет. Если в реализации невиртуального метода вызывается виртуальный метод, то

P>наследник всё-таки сможет изменить поведение метода (пусть не явно, не
P>переопределяя этот метод целиком, а лишь переопределяя вызываемые из метода
P>виртуальные методы).

P>Значит гарантировать "строгую невиртуальность" метода можно только используя

P>определённую дисциплину кодирования?

P>Зачем нужна "строгая невиртуальность"?

P>Например клиентский код хочет реализовать строго детерминированный алгоритм
P>(одинаковые данные на вход, должны давать одинаковые данные на выходе, вне
P>зависимости от того с каким наследником базового класса имеем дело).
P>Или мы хотим гарантированно обезопасить себя от побочных эффектов использования
P>кода наследника базового класса...

P>Короче, хочется, гарантий чтобы работал только код базового класса, несмотря,

P>на то что объект — это наследник.

P>Хочется странного?


Криминального в этом ни чего нет, но если хочется что бы кому-то было понятней, нужно класс и виртуальные методы сделать абстрактными, а уже от него наследовать нужную нам реализацию. Это будет паттерн "шаблонный метод"
Программа – это мысли спрессованные в код
Re[5]: Вызов виртуального метода класса из невиртуального
От: vmpire Россия  
Дата: 14.05.15 16:28
Оценка:
Здравствуйте, Pek2014, Вы писали:

P>>>А что, если ввести новое ключевое слово языка, гарантирущее, что невиртуальный метод

P>>>не вызывает виртуальных методов, т.е. при его вызове выполняется только код базового класса
P>>>и поэтому наследник никак не может повлиять на поведение этого метода?
V>>Если выделить виртуальные методы в отдельный класс, то ключевое слово уже есть: sealed (в С#) либо final (в Java)

P>Не очень понимаю, чем sealed может помочь...

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

P>Хочется иметь особо помеченные методы базового класса,

P>которые гарантированно (компилятором) не могут изменить своё поведение в производных классах.
Так можно не делать такие методы виртуальными.
Иначе непонятно, зачем разрешать их переопределять, но при этом запрещать переопределять
Ещё, как вариант, для некоторых случаев могут подойти code contracts.
Re[5]: Вызов виртуального метода класса из невиртуального
От: Sinix  
Дата: 14.05.15 16:42
Оценка: 31 (1)
Здравствуйте, Pek2014, Вы писали:


P>Хочется иметь особо помеченные методы базового класса,

P>которые гарантированно (компилятором) не могут изменить своё поведение в производных классах.
1. Вытащить критичный код в private-class (так иногда делается, это вполне нормальная практика). Вы контролируете все входы-выходы, виртуальных вызовов е будет.
2. Пометить "важные" методы атрибутами и затем написать своё правило для рослина/fxCop. Это насложно, во всяком случае, проще, чем поменять язык.

А если серьёзно — оччень рекомендую изучить Framework Design Guidelines. В ней все подобные грабли разобраны уже с десяток лет как. Ну и золотое правило из этой же книги: начните с _реальных_ сценариев использования.

Потому что сейчас всё выглядит так, как будто у вас сферическая проблема в вакууме и вы пытаетесь её решить такими же абстрактными средствами.
На практике нехоженых кем-то граблей осталось очень мало и дешевле и быстрее набрать опыта на стандартных решениях, чем набивать шишки самостоятельно
Re: Вызов виртуального метода класса из невиртуального
От: TK Лес кывт.рф
Дата: 14.05.15 21:26
Оценка:
Здравствуйте, Pek2014, Вы писали:

P>Зачем нужна "строгая невиртуальность"?

P>Например клиентский код хочет реализовать строго детерминированный алгоритм
P>(одинаковые данные на вход, должны давать одинаковые данные на выходе, вне
P>зависимости от того с каким наследником базового класса имеем дело).
P>Или мы хотим гарантированно обезопасить себя от побочных эффектов использования
P>кода наследника базового класса...

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

P>Короче, хочется, гарантий чтобы работал только код базового класса, несмотря,

P>на то что объект — это наследник.

FxCop при сборке
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re: Вызов виртуального метода класса из невиртуального
От: nikov США http://www.linkedin.com/in/nikov
Дата: 14.05.15 22:30
Оценка:
Здравствуйте, Pek2014, Вы писали:

P>Короче, хочется, гарантий чтобы работал только код базового класса, несмотря,

P>на то что объект — это наследник.

Например, VB.NET поддерживает невиртуальный вызов виртуальных методов текущего класса:

MyClass.Foo()
Re: Вызов виртуального метода класса из невиртуального
От: vorona  
Дата: 15.05.15 09:01
Оценка:
Здравствуйте, Pek2014, Вы писали:
class A
{
    public A()
    {
        this.ATest();
    }
    protected virtual void ATest()
    {
        Test();
    }
    public virtual void Test()
    {
        Console.WriteLine("A");
    }
}
class B : A
{
    protected override void ATest()
    {
        base.Test();
    }
    public override void Test()
    {
        Console.WriteLine("B");
    }
}
Re: Вызов виртуального метода класса из невиртуального
От: Sinatr Германия  
Дата: 18.05.15 07:26
Оценка: -1 :))
Здравствуйте, Pek2014, Вы писали:

P>Разбираюсь, с темой "почему в конструкторе не надо вызывать виртуальные

P>методы?" Разобрался, понял, но...

А можно ссылочку? Я не в теме, почему сабж делать не надо.

Для меня любой класс — это как пластилин, делай что хочешь. Если делать нельзя, то есть private методы (или вообще sealed/static классы). Я не совсем понимаю, что за контрактность такая, "строгая невертуальность" понимаешь. Можно конкретный пример?
---
ПроГLамеры объединяйтесь..
Re[2]: Вызов виртуального метода класса из невиртуального
От: tyomchick Россия  
Дата: 18.05.15 07:31
Оценка:
Здравствуйте, Sinatr, Вы писали:

S>Здравствуйте, Pek2014, Вы писали:


P>>Разбираюсь, с темой "почему в конструкторе не надо вызывать виртуальные

P>>методы?" Разобрался, понял, но...

S>А можно ссылочку? Я не в теме, почему сабж делать не надо.


Просто в шарпе нельзя ни через задницу проинициализировать атрибуты, до вызова конструктора предка.
Виртуальные методы могут иметь дело с неинициализированными атрибутами.
Даже самую простую задачу можно сделать невыполнимой, если провести достаточное количество совещаний
Отредактировано 18.05.2015 7:34 tyomchick . Предыдущая версия . Еще …
Отредактировано 18.05.2015 7:33 tyomchick . Предыдущая версия .
Re: Вызов виртуального метода класса из невиртуального
От: Nikolay_Ch Россия  
Дата: 19.05.15 12:31
Оценка: +2
Здравствуйте, Pek2014, Вы писали:

P>"Не вызывайте виртуальные методы класса из его невиртуальных методов".

P>Как вам такая рекомендация? Может она очевидна, а может, наоборот, это глупость?
А как быть с паттерном "Шаблонный метод"?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.