Почему нам нужно новое ключевое слово и почему поведение по умолчанию состоит в том, чтобы скрыть и не переопределить?

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

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

79
задан Olivier Jacot-Descombes 3 June 2014 в 14:50
поделиться

8 ответов

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

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

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

interface IEnumerable
{
    IEnumerator GetEnumerator();
}

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

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

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

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

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

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

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

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

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

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

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

126
ответ дан 24 November 2019 в 10:12
поделиться

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

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

2
ответ дан 24 November 2019 в 10:12
поделиться

Я предполагаю, что в основном это связано с множественным наследованием интерфейсов. При использовании незаметных интерфейсов вполне возможно, что два разных интерфейса будут использовать одну и ту же сигнатуру метода. Разрешение использования ключевого слова 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
ответ дан 24 November 2019 в 10:12
поделиться

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

5
ответ дан 24 November 2019 в 10:12
поделиться

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

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

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

15
ответ дан 24 November 2019 в 10:12
поделиться

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

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

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

-1
ответ дан 24 November 2019 в 10:12
поделиться

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

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

0
ответ дан 24 November 2019 в 10:12
поделиться

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

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

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

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

12
ответ дан 24 November 2019 в 10:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: