Каковы практические применения модификатора «new» в C # в отношении сокрытия?

21

Мы с коллегой изучали поведение newключевого слова в C #, поскольку оно относится к концепции сокрытия. Из документации :

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

Мы прочитали документацию, и мы понимаем, что она в основном делает и как это делает. То, что мы не могли понять, почему вы должны сделать это в первую очередь. Модификатор существует с 2003 года, и мы оба работаем с .Net дольше, и он никогда не появится.

Когда это поведение будет необходимо в практическом смысле (например, применительно к бизнес-кейсу)? Является ли эта функция изжившей свою полезность, или она просто достаточно необычна в том, что мы делаем (в частности, мы делаем веб-формы и приложения MVC и некоторые небольшие факторы WinForms и WPF)? Опробовав это ключевое слово и поиграв с ним, мы обнаружили некоторые варианты поведения, которые позволяют ему казаться немного опасными при неправильном использовании.

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

Джоэл Этертон
источник
9
Возможно, вы тоже это читали, но есть интересная статья Эрика Липперта (Eric Lippert) (разработчика компилятора C #) о том, почему скрытие методов было добавлено в C #: blogs.msdn.com/b/ericlippert/archive/2008/05/21/… , Это отвечает на часть вашего вопроса, но у меня нет бизнес-кейса для вас, поэтому я добавил это в комментарии.
Джалайн
3
@Jalayn: бизнес-кейс обсуждается Эриком в этом посте: blogs.msdn.com/b/ericlippert/archive/2004/01/07/…
Брайан,

Ответы:

22

Вы можете использовать его для имитации ковариации возвращаемого типа. Объяснение Эрика Липперта . Эрик предоставляет этот пример кода:

abstract class Enclosure
{
    protected abstract Animal GetContents();
    public Animal Contents() { return this.GetContents(); }
}
class Aquarium : Enclosure
{
    public new Fish Contents() { ... }
    protected override Animal GetContents() { return this.Contents(); }
}

Это обходной путь. public override Fish Contents() { ... }не является законным, несмотря на свою безопасность.

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

Скорее всего, в реальной ситуации вам понадобится скрыть метод, если поставщик базового класса добавит универсальный метод, который вы уже добавили в производный класс. Такая программа скомпилирует (и выдаст предупреждения) без ключевого слова new, но добавление newговорит: «Я знаю, что моя версия этого метода заменяет версию базового класса. Это ужасно и запутанно, но мы застряли с этим». Это все же лучше, чем заставлять производный класс переименовывать свой метод.

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

Эта ситуация подробно обсуждается в этом посте Эриком Липпертом.

Брайан
источник
1
+1 за то, newчто был маркером "это ужасно и запутанно".
Авнер Шахар-Каштан
Я думаю, что пример Эрика очень полезен, хотя бы потому, что я нахожусь в похожей ситуации ... У меня есть базовый класс, реализующий нетривиальный метод, который возвращает TBase, и производный класс, который должен возвращать TDerived. В этом случае это ощущается как неизбежное зло.
Кайл Баран
4

Я думаю, что это есть на тот случай, если вам понадобится сделать что-то, о чем дизайнеры языка могли и не подумать. C # был во многом реакцией на ранние версии Java. И одна вещь, которую сделал java, состояла в том, чтобы очень явно заразить разработчиков, чтобы исключить возможность того, что разработчики застрелились в ноги. C # выбрал немного другой подход и дал разработчикам немного больше возможностей, позволяя разработчикам еще больше возможностей выстрелить себе в ноги. Одним из примеров является unsafeключевое слово. Это newключевое слово другое.

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

Уайетт Барнетт
источник
5
У Java нет нового, потому что Java рассматривает все методы как виртуальные. По словам Эрика Липперта, мотивация для поддержки нового заключается в решении проблемы хрупкого базового класса. Существование нового необходимо для реального использования в бизнесе, а не только для низкоуровневого использования библиотеки. Java не имеет новых (и не имеет не виртуальных методов) означает, что если базовый класс вводит новый метод, который уже используется в производных классах, существующий код может сломаться, несмотря на то, что разработчик базового класса должен быть разрешен не обращать внимания на код, который его потребляет.
Брайан
3

Он говорит читателю, что «я намеренно скрыл реализацию этого метода в базовом классе», а не случайно.

Vegio
источник
5
Мне кажется, что я начинаю задавать вопрос. ОП знает, что делает, но хочет знать, почему.
Брайан
0

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

class VehicleClass
{
  public int AnyProperty
  {
    get; set;
  }

  public int AnyFunction() { return 0; }
} // VehicleClass

class IntermediateClass : VehicleClass
{
  public int PreviousAnyProperty
  {
    get { return AnyProperty; }
    set { AnyProperty = value  }
  }

  public int PreviousAnyFunction() { return AnyFunction(); }
} // IntermediateClass 

class CarClass : IntermediateClass
{
  public new int AnyProperty
  {
    get ; set ;
  }

  public new int AnyFunction() { return 5; }
} // class CarClass

class ExampleClass
{

  public static void Main()
  {
    using (CarClass MyCar = new CarClass())
    {
      int AnyInt1 = MyCar.PreviousAnyProperty;
      MyCar.PreviousAnyProperty = 7;

      int AnyInt2 = MyCar.PreviousAnyFunction();

      int AnyInt3 = MyCar.AnyProperty;
      MyCar.AnyProperty = 45;

      int AnyInt4 = MyCar.AnyFunction();
    }
  } // static void Main()

} // class CarClass

Приветствия.

umlcat
источник
Я знаком с тем, как это работает, но мой вопрос действительно так: почему вы хотите, чтобы предыдущий участник был доступен под другим именем? Это нарушает все виды контрактов, делает некоторые общие части бесполезными.
Джоэл Этертон
@Joel Etherton: Как вы уже знаете, иногда разработчикам приходится «выбирать» программный код других людей. И мы можем не иметь права изменять, возможно расширять классы. И может потребоваться использовать как предыдущий член, так и новый.
umlcat
0

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

ошалевший
источник