Почему бы не сделать все "виртуальным"?

Возможный дубликат:
Почему С# реализует методы как не виртуальные по умолчанию?

Я говорю в основном о С#,.NET 3.5, но, в общем, интересно, что преимущества не в том, чтобы считать все "виртуальным" - то есть, метод, вызванный в экземпляре дочернего класса, всегда выполняет дочерний самой версии этого метода. В С# это не так, если родительский метод не помечен "виртуальным" модификатором. Пример:

public class Parent
{
    public void NonVirtual() { Console.WriteLine("Non-Virtual Parent"); }
    public virtual void Virtual(){ Console.WriteLine("Virtual Parent"); }
}

public class Child : Parent
{
    public new void NonVirtual() { Console.WriteLine("Non-Virtual Child"); }
    public override void Virtual() { Console.WriteLine("Virtual Child"); }
}

public class Program
{
    public static void Main(string[] args)
    {
        Child child = new Child();
        Parent parent = new Child();
        var anon = new Child();

        child.NonVirtual();           // => Child
        parent.NonVirtual();          // => Parent
        anon.NonVirtual();            // => Child
        ((Parent)child).NonVirtual(); // => Parent

        child.Virtual();              // => Child
        parent.Virtual();             // => Child
        anon.Virtual();               // => Child
        ((Parent)child).Virtual();    // => Child
    }
}

В чем конкретно проявляются преимущества не виртуального поведения выше? Единственное, о чем я мог подумать, было "Что, если автор Родитель не хочет, чтобы его метод был виртуальным?" но потом я понял, что не могу придумать для этого хороший вариант использования. Можно утверждать, что поведение класса зависит от того, как работает не виртуальный метод, - но тогда мне кажется, что происходит некоторая плохая инкапсуляция или что метод должен быть запечатан.

Вдоль этих же строк, похоже, что "скрытие" - это, как правило, плохая идея. В конце концов, если был создан объект и методы Child, кажется, что это было сделано по определенной причине для переопределения родителя. И если Child реализует (и скрывает родителей) NonVirtual(), очень легко не получить то, что многие могут считать "ожидаемым" поведением вызова Child:: NonVirtual(). (Я говорю "ожидаемый", потому что иногда легко заметить, что "скрытие" происходит).

Итак, каковы преимущества того, чтобы не позволять всем иметь "виртуальное" поведение? Что является хорошим прецедентом для скрытия не виртуального родителя, если так легко получить неожиданное поведение?

Если кому-то интересно, почему я задаю этот вопрос - я недавно изучал библиотеку DynamicProxy Castle Projects. Единственное препятствие в его использовании заключается в том, что любой метод (или свойство), который вы хотите прокси, должен быть виртуальным. И это не всегда вариант для разработчиков (если у нас нет контроля над источником). Не говоря уже о цели DynamicProxy - избегать связи между вашим прокси-классом и любым поведением, которое вы пытаетесь достичь с помощью прокси-сервера (например, Logging или, возможно, реализация Memoization). И, заставляя виртуальные методы выполнять это, вместо этого достигается очень тонкая, но тупое взаимодействие DynamicProxy со всеми классами, которые он проксирует. Представьте, у вас есть тонна методов, обозначенных виртуальными, даже если они никогда не унаследованы и не переопределены, поэтому любые другие разработчик, смотрящий на код, может задаться вопросом: "Почему они даже виртуальные? позволяют изменить их".

В любом случае, разочарование привело меня к удивлению, что преимущества не виртуального, когда кажется, что все виртуальное, возможно, было более ясным (IMO, я полагаю) и, возможно, (?) имеют больше преимуществ.

EDIT: Маркировка как вики сообщества, так как это похоже на вопрос, который может иметь субъективные ответы

Ответы

Ответ 2

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

Ответ 3

Во многих случаях важно, чтобы класс правильно функционировал, что данный метод имеет определенное поведение. Если метод переопределен в унаследованном классе, нет гарантии, что метод будет правильно реализовывать ожидаемое поведение. Вы должны только отмечать метод virtual, если ваш класс специально предназначен для наследования и будет поддерживать метод с другой реализацией. Проектирование для наследования непросто, есть много случаев, когда неправильное переопределение метода нарушает внутреннее поведение класса

Ответ 4

Простой: вся точка в классе заключается в инкапсуляции какой-либо абстракции. Например, нам нужен объект, который ведет себя как текстовая строка.

Теперь, если бы все было виртуально, я мог бы это сделать:

class MessedUpString : String{
   override void Trim() { throw new Exception(); }
}

а затем передать это некоторой функции, которая ожидает строку. И как только они пытаются обрезать эту строку, она взрывается.

Строка больше не ведет себя как строка. Как эта когда-либо хорошая вещь?

Если все сделано виртуально, вам будет сложно выполнять инварианты класса. Вы разрешаете абстракцию класса.

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

Классы причин полезны в том, что они позволяют нам игнорировать детали реализации. Мы можем просто сказать: "Это строковый объект, я знаю, что он будет вести себя как строка. Я знаю, что он никогда не нарушит ни одну из этих гарантий". Если эта гарантия не может быть сохранена, класс бесполезен. Вы могли бы просто сделать все члены данных общедоступными и перенести методы-члены вне класса.

Знаете ли вы Принцип замены Лискова? В любом случае, когда ожидается объект базового класса B, вы должны иметь возможность передать объект производного класса D. Это одно из самых фундаментальных правил объектно-ориентированного программирования. Нам нужно знать, что производные классы будут по-прежнему работать, когда мы повышаем их до базового класса и передаем их функции, ожидающей базовый класс. Это означает, что мы должны сделать какое-то поведение фиксированным и неизменным.

Ответ 5

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

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

Ответ 6

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