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

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

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

Ответ 1

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

Цель этого проектного решения - помочь смягчить класс проблем, известных как проблемы "Хрупкого базового класса". Вот версия этой проблемы:

Foo Corporation создает класс Frobber и отправляет его в Foo.DLL версии 1.0:

namespace FooCorp
{
  public class Frobber
  {
    public void Frobnicate() { ... }
    ...

Bar Corporation, с которой вы работаете, делает Blobbers. Blobber может делать все, что может сделать Frobber, но, кроме того, он может также Blobnicate. Поэтому вы решили повторно использовать реализацию Frobnicate из FooCorp и добавить некоторые дополнительные функции:

namespace BarCorp
{
  public class Blobber : FooCorp.Frobber
  {
    public void Blobnicate() { ... }
    ...

Корпорация Foo понимает, что людям нравится Blobnicate, и они решили отправить Foo.DLL v2.0:

namespace FooCorp
{
  public class Frobber
  {
    public void Frobnicate() { ... }
    public void Blobnicate() { ... }
    ...

Когда вы получаете новую версию Foo.DLL и перекомпилируете, , вам нужно сообщить, что вы случайно вводите новый метод, который затеняет метод базового класса. Это, возможно, опасная вещь делать; ваш класс был написан с предположением, что базовый класс был Frobnicator, но, видимо, теперь это тоже Blobnator! Этот факт может сломать ваших клиентов, которые могут случайно вызвать версию базового класса, когда они намереваются вызвать версию вашего производного класса.

Мы делаем "новый" факультативным, чтобы легализовать теневой метод базового класса без изменения исходного кода. Если мы сделаем это незаконным, FooCorp сломал бы вашу сборку с обновлением. Но мы делаем это предупреждением, чтобы вы знали, что можете делать это случайно. Затем вы можете внимательно изучить код; если вы решите, что ваша реализация Blobnicate теперь избыточна, вы можете удалить ее. Если это все еще хорошо, вы можете пометить его как "новое" и устранить предупреждение.

Имеют смысл? Это одна из тонких функций С#, которые делают ее пригодной для крупномасштабного программного обеспечения, ориентированного на несколько версий.

Ответ 2

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

Однако, если вам действительно не нужно скрывать элемент, обычно рекомендуется использовать virtual/override, чтобы использовать член полиморфно. При скрытии член new используется только через ссылку производного класса. Если вы работаете через базовую ссылку, вы будете продолжать получать базовое поведение, и это может быть неожиданным.

class Foo 
{
     public void M() { Console.WriteLine("Foo"); }
}

class Bar : Foo
{
     public new void M() { Console.WriteLine("Bar"); }
}

Bar bar = new Bar(); 
bar.M(); // writes Bar
Foo foo = new Bar(); // still an instance of Bar
foo.M(); // writes Foo, does not use "new" method defined in Bar

Если вместо этого метод был объявлен с virtual в Foo и override в Bar, тогда вызов метода был бы последовательно "Бар" в любом случае.

Ответ 3

Ключевое слово new позволяет вам быть конкретным в ваших намерениях.

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

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