Подтвердить что ты не робот

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

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

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

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

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

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?
}
4b9b3361

Ответ 1

Рассмотрим:

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

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

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

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

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

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

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

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

Ответ 2

Переопределение - это когда вы предоставляете новую реализацию метода 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
Запуск метода BaseClass2

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


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

Ответ 3

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

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

как

class Foo  
{  
  public virtual void foo1()  
  {  

  }  
}  

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

  }  
}  

теперь, если вы создадите еще один класс Bar1, который является производным от Bar, вы можете переопределить foo1, который находится в баре.