Переопределение против скрытия метода

Я немного озадачен переопределением или сокрытием метода в C#. Практическое использование каждого также будет оценено, а также объяснение того, когда каждый будет использовать каждый.

Я запутался в переопределении - почему мы переопределяем? До сих пор я узнал, что путем переопределения мы можем обеспечить желаемую реализацию метода производного класса, не меняя сигнатуру.

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

Меня также смущает следующее: что это демонстрирует?

class A
{
    virtual m1()
    {
        console.writeline("Bye to all");
    }
}

class B : A
{
    override m1()
    {
        console.writeLine("Hi to all");
    }
}

class C
{
    A a = new A();
    B b = new B();
    a = b; (what is this)
    a.m1(); // what this will print and why?

    b = a; // what happens here?
}

3 ответа

Решение

Рассматривать:

public class BaseClass
{
  public void WriteNum()
  {
    Console.WriteLine(12);
  }
  public virtual void WriteStr()
  {
    Console.WriteLine("abc");
  }
}

public class DerivedClass : BaseClass
{
  public new void WriteNum()
  {
    Console.WriteLine(42);
  }
  public override void WriteStr()
  {
    Console.WriteLine("xyz");
  }
}
/* ... */
BaseClass isReallyBase = new BaseClass();
BaseClass isReallyDerived = new DerivedClass();
DerivedClass isClearlyDerived = new DerivedClass();

isReallyBase.WriteNum(); // writes 12
isReallyBase.WriteStr(); // writes abc
isReallyDerived.WriteNum(); // writes 12
isReallyDerived.WriteStr(); // writes xyz
isClearlyDerived.WriteNum(); // writes 42
isClearlyDerived.writeStr(); // writes xyz

Переопределение - это классический ОО-способ, с помощью которого производный класс может иметь более конкретное поведение, чем базовый класс (в некоторых языках у вас нет другого выбора, кроме как сделать это). Когда для объекта вызывается виртуальный метод, вызывается самая производная версия метода. Следовательно, хотя мы имеем дело с isReallyDerived как BaseClass тогда функциональность определена в DerivedClass используется.

Сокрытие означает, что у нас совершенно другой метод. Когда мы звоним WriteNum() на isReallyDerived тогда нет никакого способа узнать, что есть другой WriteNum() на DerivedClass так что это не называется. Его можно вызвать только тогда, когда мы имеем дело с объектом как DerivedClass,

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

  1. Прямая совместимость. Если DerivedClass был DoStuff() метод, а затем позже BaseClass был изменен, чтобы добавить DoStuff() метод, (помните, что они могут быть написаны разными людьми и существуют в разных собраниях), тогда запрет на скрытие членов внезапно сделал бы DerivedClass глючит без изменения. Кроме того, если новый DoStuff() на BaseClass был виртуальным, а затем автоматически делает это на DerivedClass его переопределение может привести к тому, что уже существующий метод будет вызван, когда он не должен. Следовательно, хорошо, что скрытие используется по умолчанию (мы используем new чтобы прояснить это, мы определенно хотим скрыть, но, оставляя это вне, скрывает и выдает предупреждение при компиляции).

  2. Ковариантность бедняков. Рассмотрим Clone() метод на BaseClass который возвращает новый BaseClass это копия того, что создано. В переопределении на DerivedClass это создаст DerivedClass но вернуть его как BaseClassчто не так полезно. Что мы могли бы сделать, это иметь виртуальную защиту CreateClone() это отменено. В BaseClass у нас есть Clone() который возвращает результат этого - и все хорошо - в DerivedClass мы скрываем это с новым Clone() который возвращает DerivedClass, призвание Clone() на BaseClass всегда будет возвращать BaseClass ссылка, которая будет BaseClass значение или DerivedClass значение по мере необходимости. призвание Clone() на DerivedClass вернет DerivedClass значение, которое мы хотели бы в этом контексте. Существуют и другие варианты этого принципа, однако следует отметить, что все они довольно редки.

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

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

Переопределение, когда вы предоставляете новый override реализация метода в классе-потомке, когда этот метод определен в базовом классе как virtual,

Скрытие - это когда вы предоставляете новую реализацию метода в классе-потомке, когда этот метод не определен в базовом классе как virtualили когда ваша новая реализация не указывает override,

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

Например, рассмотрим эти классы:

public class BaseClass
{
  public virtual void Method1()  //Virtual method
  {
    Console.WriteLine("Running BaseClass Method1");
  }
  public void Method2()  //Not a virtual method
  {
    Console.WriteLine("Running BaseClass Method2");
  }
}
public class InheritedClass : BaseClass
{
  public override void Method1()  //Overriding the base virtual method.
  {
    Console.WriteLine("Running InheritedClass Method1");
  }
  public new void Method2()  //Can't override the base method; must 'new' it.
  {
    Console.WriteLine("Running InheritedClass Method2");
  }
}

Давайте назовем это так с экземпляром InheritedClass в соответствующей ссылке:

InheritedClass inherited = new InheritedClass();
inherited.Method1();
inherited.Method2();

Это возвращает то, что вы должны ожидать; оба метода говорят, что они работают с версиями InheritedClass.

Запуск метода InheritedClass1
Запуск метода InheritedClass2

Этот код создает экземпляр того же InheritedClass, но сохраняет его в ссылке BaseClass:

BaseClass baseRef = new InheritedClass();
baseRef.Method1();
baseRef.Method2();

Обычно в соответствии с принципами ООП вы должны ожидать того же результата, что и в приведенном выше примере. Но вы не получите тот же результат:

Запуск метода InheritedClass1
Запуск BaseClass Method2

Когда вы писали код InheritedClass, возможно, вы хотели, чтобы все вызовы Method2() запустить код, который вы написали в нем. Обычно это будет так, если вы работаете с virtual метод, который вы переопределили. Но потому что вы используете new/ скрытый метод, он вызывает версию по ссылке, которую вы используете, вместо этого.


Если это поведение, которое вы действительно хотите, то; вот и ты. Но я настоятельно рекомендую, что если это то, что вы хотите, может быть большая архитектурная проблема с кодом.

Переопределение метода - это просто переопределение реализации метода базового класса по умолчанию в производном классе.

Сокрытие метода: вы можете использовать ключевое слово "new" перед виртуальным методом в производном классе

как

class Foo  
{  
  public virtual void foo1()  
  {  

  }  
}  

class Bar:Foo  
{  
  public new virtual void foo1()  
  {   

  }  
}  

Теперь, если вы создадите другой класс Bar1, производный от Bar, вы можете переопределить foo1, который определен в Bar.

Другие вопросы по тегам