Переопределение против скрытия метода
Я немного озадачен переопределением или сокрытием метода в 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
,
Большую часть времени прятаться плохо. Как правило, либо у вас должен быть метод как виртуальный, если он может быть изменен в производном классе, и переопределите его в производном классе. Однако есть две вещи, для которых это полезно:
Прямая совместимость. Если
DerivedClass
былDoStuff()
метод, а затем позжеBaseClass
был изменен, чтобы добавитьDoStuff()
метод, (помните, что они могут быть написаны разными людьми и существуют в разных собраниях), тогда запрет на скрытие членов внезапно сделал быDerivedClass
глючит без изменения. Кроме того, если новыйDoStuff()
наBaseClass
был виртуальным, а затем автоматически делает это наDerivedClass
его переопределение может привести к тому, что уже существующий метод будет вызван, когда он не должен. Следовательно, хорошо, что скрытие используется по умолчанию (мы используемnew
чтобы прояснить это, мы определенно хотим скрыть, но, оставляя это вне, скрывает и выдает предупреждение при компиляции).Ковариантность бедняков. Рассмотрим
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.