Зачем нам новое ключевое слово и почему поведение по умолчанию скрывает, а не переопределяет? - PullRequest
78 голосов
/ 25 июня 2010

Я просматривал это сообщение в блоге и задавал следующие вопросы:

  • Зачем нам нужно ключевое слово new, просто чтобы указать, что метод базового класса скрыт. Я имею в виду, зачем нам это нужно? Если мы не используем ключевое слово override, не скрываем ли мы метод базового класса?
  • Почему в C # по умолчанию скрывается, а не переопределяется? Почему дизайнеры реализовали это таким образом?

Ответы [ 8 ]

125 голосов
/ 25 июня 2010

Хорошие вопросы.Позвольте мне переформулировать их.

Почему законно вообще скрывать метод с другим методом?

Позвольте мне ответить на этот вопрос с примером.У вас есть интерфейс от CLR v1:

interface IEnumerable
{
    IEnumerator GetEnumerator();
}

Super.Теперь в CLR v2 у вас есть дженерики, и вы думаете: «мужик, если бы у нас были дженерики в v1, я бы сделал это универсальным интерфейсом. Но я не сделал. универсальный, так что я получаю преимущества универсальных шаблонов, не теряя обратной совместимости с кодом, который ожидает IEnumerable. "

interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> .... uh oh

Как вы собираетесь вызывать метод GetEnumerator IEnumerable<T>?Помните, вы хотите скрыть GetEnumerator в неуниверсальном базовом интерфейсе.Вы никогда не хотите, чтобы эта вещь вызывалась, если вы явно не находитесь в ситуации обратного сжатия.

Одно это оправдывает сокрытие метода.Для получения дополнительной информации об обоснованиях скрытия методов см. мою статью на эту тему .

Почему скрытие без «нового» вызывает предупреждение?

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

Почему скрывается без "новое "предупреждение, а не ошибка?

По той же причине.Вы можете что-то скрывать случайно, потому что вы только что подобрали новую версию базового класса.Это происходит все время.FooCorp создает базовый класс B. BarCorp создает производный класс D с методом Bar, потому что его клиентам нравится этот метод.FooCorp видит это и говорит: эй, это хорошая идея, мы можем поместить эту функциональность в базовый класс.Они делают это и поставляют новую версию Foo.DLL, и когда BarCorp забирает новую версию, было бы хорошо, если бы им сказали, что их метод теперь скрывает метод базового класса.

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

Почему скрывается и не переопределяется значение по умолчанию?

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

14 голосов
/ 25 июня 2010

Если методу в производном классе предшествует ключевое слово new, метод определяется как независимый от метода в базовом классе

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

Так что это поможет вам избежать ошибок и явно показать, что вы хотите сделать, и сделает код более читабельным, чтобы вы могли легко понять ваш код.

12 голосов
/ 25 июня 2010

Стоит отметить, что эффект only new в этом контексте заключается в подавлении предупреждения. В семантике нет изменений.

Итак, один из ответов таков: нам нужно new, чтобы сообщить компилятору, что сокрытие является преднамеренным и избавиться от предупреждения.

Следующий вопрос: если вы не хотите / не можете переопределить метод, зачем вам вводить другой метод с тем же именем? Потому что сокрытие - это конфликт имен. И вы, конечно же, избежали бы этого в большинстве случаев.

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

5 голосов
/ 25 июня 2010

В C # члены запечатаны по умолчанию, что означает, что вы не можете переопределить их (если только они не помечены ключевыми словами virtual или abstract) и это из соображений производительности . Модификатор new используется для явного скрытия унаследованного члена.

2 голосов
/ 25 июня 2010

Если переопределение было по умолчанию без указания ключевого слова override, вы можете случайно переопределить какой-либо метод вашей базы только из-за равенства имен.

. Net стратегия компилятора состоит в том, чтобы выдавать предупреждения, если что-то может пойти не такпросто для безопасности, поэтому в этом случае, если переопределение было по умолчанию, для каждого переопределенного метода должно быть предупреждение - что-то вроде «предупреждение: проверьте, действительно ли вы хотите переопределить».

0 голосов
/ 26 июня 2010

Как уже отмечалось, скрытие метода / свойства позволяет изменить вещи о методе или свойстве, которые иначе нельзя было бы легко изменить. Одна из ситуаций, в которой это может быть полезно, - позволить унаследованному классу иметь свойства чтения-записи, которые доступны только для чтения в базовом классе. Например, предположим, что базовый класс имеет набор свойств только для чтения, называемых Value1-Value40 (конечно, реальный класс будет использовать более подходящие имена). Запечатанный потомок этого класса имеет конструктор, который берет объект базового класса и копирует значения оттуда; класс не позволяет их менять после этого. Другой наследуемый потомок объявляет свойства чтения-записи с именем Value1-Value40, которые при чтении ведут себя так же, как версии базового класса, но при записи позволяют записывать значения. Чистым эффектом будет то, что код, который хочет, чтобы экземпляр базового класса, который, как он знает, никогда не изменялся, мог создать новый объект класса только для чтения, который может копировать данные из переданного объекта, не беспокоясь о том, является ли этот объект только для чтения или для чтения и записи.

Одно раздражение при таком подходе - возможно, кто-то может помочь мне - это то, что я не знаю способа скрывать и переопределять определенное свойство в пределах одного и того же класса. Разрешает ли это какой-либо из языков CLR (я использую vb 2005)? Было бы полезно, если бы объект базового класса и его свойства могли быть абстрактными, но это потребовало бы, чтобы промежуточный класс переопределил свойства Value1 to Value40, прежде чем класс-потомок мог их затенить.

0 голосов
/ 25 июня 2010

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

Обновлено ... Эрик подсказал мне, как улучшить этот пример.

public interface IAction1
{
    int DoWork();
}
public interface IAction2
{
    string DoWork();
}

public class MyBase : IAction1
{
    public int DoWork() { return 0; }
}
public class MyClass : MyBase, IAction2
{
    public new string DoWork() { return "Hi"; }
}

class Program
{
    static void Main(string[] args)
    {
        var myClass = new MyClass();
        var ret0 = myClass.DoWork(); //Hi
        var ret1 = ((IAction1)myClass).DoWork(); //0
        var ret2 = ((IAction2)myClass).DoWork(); //Hi
        var ret3 = ((MyBase)myClass).DoWork(); //0
        var ret4 = ((MyClass)myClass).DoWork(); //Hi
    }
}
0 голосов
/ 25 июня 2010

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

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

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

...