Зачем вам нужно скрывать метод с помощью `new`?

Возможный дубликат:
C # - новое ключевое слово в сигнатуре метода

Допустим, у меня 3 класса: дедушка, папа, сын. Сын унаследовал от папы, что унаследовал от дедушки.

Каждый класс реализует foo.

// GrandDad class: 
public virtual void foo()

// Dad class: 
new public virtual void foo()

// Son class: 
public override void foo()

Я не понимаю, почему папа использовал ключевое слово new. Насколько я понимаю, использование new скрывает метод. Зачем тебе это нужно?

Я прочитал объяснение нового MSDN, но обсуждение было только механическим, а не архитектурным.

Спасибо


person Kevin Meredith    schedule 25.08.2011    source источник
comment
Обычно вы видите это в контексте невиртуальных методов. Странный случай.   -  person Jeff Mercado    schedule 25.08.2011
comment
Ответы на этот вопрос SO могут помочь: stackoverflow .com / questions / 1014295 /.   -  person Ben Gribaudo    schedule 25.08.2011


Ответы (6)


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

person Dan Bryant    schedule 25.08.2011

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

public class Base
{
    public virtual void SomeMethod()
    {
    }
}

public class Derived : Base
{
    public override void SomeMethod()
    {
    }
}

...

Base b = new Derived();
b.SomeMethod();

в конечном итоге вызовет Derived.SomeMethod, если он переопределяет Base.SomeMethod.

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

public class Base
{
    public virtual void SomeOtherMethod()
    {
    }
}

public class Derived : Base
{
    public new void SomeOtherMethod()
    {
    }
}

...


Base b = new Derived();
Derived d = new Derived();
b.SomeOtherMethod();
d.SomeOtherMethod();

Сначала вызовет Base.SomeOtherMethod, затем Derived.SomeOtherMethod. По сути, это два совершенно разных метода, которые имеют одно и то же имя, а не производный метод, переопределяющий базовый метод.

person CharithJ    schedule 25.08.2011

Сценарий, который может быть «полезным», когда у вас есть иерархия классов на нескольких уровнях (Control, Button, RedButton) и вы хотите, чтобы любой подкласс данного класса (Button) был ограничен для изменения общего поведения, унаследованного от Control, то есть изменено кнопкой; играть по правилам кнопок, а не по правилам управления, иначе RedButton может стать TreeViewControl :) ...; Это может произойти, когда вы наследуете реализацию :)

Существуют и другие техники, более чистые, такие как «шаблон стратегии» (вы агрегируете поведение, а не наследуете), которые используются для обработки предыдущего сценария.

person Nicolae Dascalu    schedule 25.08.2011

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

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

Person aperson = new Person();
aperson.Add(anAddress);
aperson.Add(anAddress);

Если вы хотите, чтобы метод Add () выполнял некоторую бизнес-логику при вставке, у вас есть выбор. Либо вы реализуете все функции контейнера самостоятельно (фу), либо наследуете свой класс Person от контейнера:

public class Person : List<Address> { }

Теперь вы можете получить желаемое поведение при двух условиях:

  1. Не ссылайтесь на класс Person по его базовому типу (List) и ...
  2. Используйте ключевое слово 'new', чтобы скрыть операцию добавления базового списка.

Ключевое слово new необходимо, потому что List.Add (...) не является виртуальным. Это несколько нехорошо (возможно, плохо), но в данном конкретном случае в основном круто. В создаваемый вами метод «new Add (...)» вы включаете свою бизнес-логику, а затем вызываете базовый метод Add (...).

Хотите сделать это еще круче? Сделайте дерево:

public class Node : List<Node> { }

В видеоиграх я использовал этот прием при написании дерева игры.

person Brent Arias    schedule 25.08.2011

Вот одна из причин, по которой вы захотите это сделать:

// GrandDad: 
public void CanIWatchTv() {
   return age > 5;
}

// Dad: Grandpa is too liberal
public override void CanIWatchTv() {
   return age > 12;
}

// Son: I don't care what dad says!
public new void CanIWatchTv() {
   return true;
}

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

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

person Mrchief    schedule 25.08.2011
comment
@Downvoter: Я ожидал, что такая форма объяснения может не понравиться всем, но если вы объясните почему, возможно, я смогу импровизировать ... - person Mrchief; 25.08.2011
comment
Если классы спроектированы правильно, замена derivedClassVariable.DoSomething(whatever) на ((BaseClass) производнаяClassVariable) .DoSomething (something) `обычно либо приводит к ошибке компиляции, либо ведет себя идентично. Бывают случаи, когда можно с пользой использовать new в методе и при этом придерживаться таких ожиданий, но, как правило, не следует использовать new в целях его нарушения. - person supercat; 14.06.2013

Насколько я понимаю, одна из основных мотиваций связана с управлением версиями библиотек с течением времени.

1 января: Компания X выпускает класс Dad как часть Super Library v1.0.

class Dad
{
    virtual public void Foo() { ... };
}

1 февраля: вы покупаете Super Library v1.0 и создаете подкласс Son.

class Son : Dad
{
    public void Bar() { ... };
} 

1 марта: Компания X выпускает Super Library 2.0, добавляя новый метод под названием Bar. Они понятия не имеют, что вы также создали метод Bar, и их Bar совершенно не связан.

class Dad
{
    virtual public void Foo() { ... };
    virtual public void Bar() { ... };
}

1 мая: вы покупаете Super Library v2.0 и перекомпилируете свой класс Son для этого. К вашему удивлению, вы обнаружите, что они добавили метод под названием Bar, который конфликтует с вашим. Чтобы свести к минимуму риск регресса, теперь вы помечаете свой метод панели как новый, чтобы отличать его от панели компании X. Оба мирно сосуществуют, хотя у них странно одно и то же имя. Их существующий код будет вызывать их Bar, как и ожидалось, а ваш существующий код будет вызывать ваш Bar, как ожидалось.

class Son : Dad
{
    new public void Bar() { ... };
}
person C. Dragon 76    schedule 25.08.2011