Різниця між новим і переоціненим


198

Цікаво, яка різниця між наступним:

Випадок 1: Базовий клас

public void DoIt();

Випадок 1: Спадковий клас

public new void DoIt();

Випадок 2: Базовий клас

public virtual void DoIt();

Випадок 2: Спадковий клас

public override void DoIt();

Здається, що обидва випадки 1 і 2 мають однаковий ефект на основі проведених тестів. Чи є різниця, або бажаний спосіб?


2
Дублікат багатьох питань, включаючи stackoverflow.com/questions/159978/…
Джон Скіт

Відповіді:


267

Модифікатор заміщення може використовуватися у віртуальних методах і повинен використовуватися на абстрактних методах. Це вказує на те, що компілятор повинен використовувати останню визначену реалізацію методу. Навіть якщо метод викликається посиланням на базовий клас, він використовуватиме реалізацію, що його перекриває.

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

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

Base b = new Derived();
b.DoIt();                      // Calls Derived.DoIt

зателефонує, Derived.DoItякщо це відмінить Base.DoIt.

Новий модифікатор доручає компілятору використовувати реалізацію дочірнього класу замість батьківського класу. Будь-який код, який не посилається на ваш клас, але батьківський клас використовуватиме реалізацію батьківського класу.

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

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

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

b.DoIt();                      // Calls Base.DoIt
d.DoIt();                      // Calls Derived.DoIt

Спочатку подзвонить Base.DoIt, потімDerived.DoIt . Вони фактично є двома цілком окремими методами, які мають однакове ім'я, а не похідний метод, що переосмислює базовий метод.

Джерело: Блог Microsoft


5
This indicates for the compiler to use the last defined implementation of a method. як можна знайти останню визначену реалізацію методу ??
AminM

5
Почніть з конкретного класу, перевірте, чи є в ньому реалізація цікавого методу. Якщо це так, ви закінчили. Якщо це не так, перейдіть на один крок в ієрархії спадкування, тобто перевірте, чи має суперклас цікавий метод. Продовжуйте, поки не знайдете цікавий метод.
csoltenborn

2
Також зауважте, що ви можете overrideвикористовувати метод лише тоді, коли базовий клас визначає метод як virtual. Слово virtualє базовим класом кажучи : «Гей, коли я називаю цей метод, він міг би фактично був замінений похідною реалізацією, так що я не знаю заздалегідь , який метод реалізації я на самому ділі виклику під час виконання. Так virtualЗначить є заповнення заповнення методу. Це означає, що методи, які не позначені як virtualне можуть бути відмінені, але ви можете замінити будь-який невіртуальний метод у похідному класі на модифікатор new, доступний лише на похідному рівні.
Ерік Бонгерс

177

віртуальний : вказує на те, що метод може бути замінений спадкоємцем

перевизначення : переосмислює функціональність віртуального методу в базовому класі, забезпечуючи різні функціональні можливості.

новий : приховує оригінальний метод (який не повинен бути віртуальним), надаючи різні функціональні можливості. Це слід використовувати лише там, коли це абсолютно необхідно.

Коли ви приховуєте метод, ви все одно можете отримати доступ до оригінального методу, перенісши його до базового класу. Це корисно в деяких сценаріях, але небезпечно.


2
Чому викид методу, який приховує базовий метод, небезпечний? Або ти маєш на увазі, що загалом кастинг небезпечний?
Марк

3
@Mark - абонент може не знати про реалізацію, що спричинить випадкове використання.
Джон Б

Чи можете ви використовувати overrideта / або newбез virtualбатьківського методу?
Аарон Франке

16

У першому випадку ви ховаєте визначення у батьківському класі. Це означає, що його буде викликано лише тоді, коли ви маєте справу з об'єктом як дочірній клас. Якщо ви присвоїли класу його батьківський тип, буде використаний метод батьків. У другому випадку метод переосмислюється і буде викликатись незалежно від того, чи передається об'єкт як дочірній або батьківський клас.


7

спробуйте наступне: (case1)

((BaseClass)(new InheritedClass())).DoIt()

Редагувати: віртуальний + переопрацьований вирішується під час виконання (тому переосмислення дійсно переосмислює віртуальні методи), а новий просто створює новий метод з тим самим іменем та приховує старий, він вирішується під час компіляції -> ваш компілятор називатиме метод це ' бачить '


3

У випадку 1, якщо ви використовували виклик методу DoIt () спадкового класу, тоді як тип оголошується базовим класом, ви побачите навіть дію базового класу.

/* Results
Class1
Base1
Class2
Class2
*/
public abstract class Base1
{
    public void DoIt() { Console.WriteLine("Base1"); }
}
public  class Class1 : Base1 
{
    public new void DoIt() { Console.WriteLine("Class1"); }
}
public abstract class Base2
{
    public virtual void DoIt() { Console.WriteLine("Base2"); }
}
public class Class2 : Base2
{
    public override void DoIt() { Console.WriteLine("Class2"); }
}
static void Main(string[] args)
{
    var c1 = new Class1();
    c1.DoIt();
    ((Base1)c1).DoIt();

    var c2 = new Class2();
    c2.DoIt();
    ((Base2)c2).DoIt();
    Console.Read();
}

Чи можете ви опублікувати попередження або помилку, яку ви отримуєте. Цей код добре працював, коли я його первісно опублікував.
Матвій Віт

Це все слід вставити у ваш клас вступу (Програма). Це було видалено для кращого форматування на цьому сайті.
Матвій Віт

3

Різниця між двома випадками полягає в тому, що у випадку 1 базовий DoItметод не перекривається, а просто приховується. Це означає, що залежно від типу змінної залежить від того, який метод буде викликаний. Наприклад:

BaseClass instance1 = new SubClass();
instance1.DoIt(); // Calls base class DoIt method

SubClass instance2 = new SubClass();
instance2.DoIt(); // Calls sub class DoIt method

Це може бути дуже заплутаним і призводить до не очікуваної поведінки, і слід уникати, якщо це можливо. Тож кращим способом був би випадок 2.


3
  • newозначає поважати тип СПОСОБ (зліва =), тим самим використовуючи метод еталонних типів. Якщо у визначеному методі немає newключового слова, він поводиться так, як є. Більше того, він також відомий як неполіморфне успадкування . Тобто, "я роблю абсолютно новий метод у похідному класі, який абсолютно не має нічого спільного з жодними методами з тим же ім'ям в базовому класі". - сказав Вітакер
  • override, яке повинно бути використане з virtualключовим словом у його базовому класі, означає поважати тип OBJECT (права частина =), тим самим запускаючи метод, який переосмислюється незалежно від типу посилання. Більше того, це також відоме як поліморфне успадкування .

Мій спосіб мати на увазі обидва ключові слова, що вони протилежні один одному.

override: virtualключове слово має бути визначене, щоб замінити метод. Метод, що використовує overrideключове слово, яке незалежно від типу посилання (посилання базового класу або похідного класу), якщо воно інстанціюється з базовим класом, працює методом базового класу. Інакше працює метод похідного класу.

new: якщо ключове слово використовується методом, на відміну від overrideключового слова, важливим є тип посилання. Якщо він призначений для похідного класу, а тип опор є базовим класом, метод базового класу працює. Якщо він інстанціюється з похідним класом і посилальним типом є похідний клас, метод похідного класу працює. А саме, це контраст overrideключового слова. Якщо пасажир, якщо ви забудете або пропустите додати нове ключове слово до методу, компілятор веде себе за замовчуванням, оскільки newвикористовується ключове слово.

class A 
{
    public string Foo() 
    {
        return "A";
    }

    public virtual string Test()
    {
        return "base test";
    }
}

class B: A
{
    public new string Foo() 
    {
        return "B";
    }
}

class C: B 
{
    public string Foo() 
    {
        return "C";
    }

    public override string Test() {
        return "derived test";
    }
}

Телефонувати в основному:

A AClass = new B();
Console.WriteLine(AClass.Foo());
B BClass = new B();
Console.WriteLine(BClass.Foo());
B BClassWithC = new C();
Console.WriteLine(BClassWithC.Foo());

Console.WriteLine(AClass.Test());
Console.WriteLine(BClassWithC.Test());

Вихід:

A
B
B
base test
derived test

Приклад нового коду,

Грайте з кодом, коментуючи по одному.

class X
{
    protected internal /*virtual*/ void Method()
    {
        WriteLine("X");
    }
}
class Y : X
{
    protected internal /*override*/ void Method()
    {
        base.Method();
        WriteLine("Y");
    }
}
class Z : Y
{
    protected internal /*override*/ void Method()
    {
        base.Method();
        WriteLine("Z");
    }
}

class Programxyz
{
    private static void Main(string[] args)
    {
        X v = new Z();
        //Y v = new Z();
        //Z v = new Z();
        v.Method();
}

1

Якщо ключове слово overrideвикористовується в класі виведення, то його переосмислює батьківський метод.

Якщо ключове слово newвикористовується в класі виведення, то метод виведення, прихований батьківським методом.


1

У мене було те саме питання, і це дуже заплутано, ви повинні врахувати, що переосмислення та нові ключові слова, що працюють лише з об'єктами базового класу та значенням похідного класу. У цьому випадку ви побачите лише ефект переосмислення та нового: Отже, якщо ви маєте class Aі B, Bуспадковуєте його A, ви інстанціюєте такий об’єкт:

A a = new B();

Тепер методи виклику будуть враховувати його стан. Перевизначення : означає, що він розширює функцію методу, тоді він використовує метод у похідному класі, тоді як новий скаже компілятору, щоб приховати метод у похідному класі та використовувати метод у базовому класі. Ось дуже хороший погляд на цю тему:

https://msdn.microsoft.com/EN-US/library/ms173153%28v=VS.140,d=hv.2%29.aspx?f=255&MSPPError=-2147217396


1

Стаття нижче розміщена на vb.net, але я думаю, що пояснення щодо нових переосмислень дуже легко зрозуміти.

https://www.codeproject.com/articles/17477/the-dark-shadow-of-overrides

У якийсь момент статті є таке речення:

Загалом, Shadows припускає, що функція, пов'язана з типом, викликається, тоді як Overrides припускає, що реалізація об'єкта виконується.

Прийнята відповідь на це питання є ідеальною, але я думаю, що ця стаття надає хороші приклади, щоб додати кращого сенсу щодо відмінностей між цими двома ключовими словами.


1

З усіх тих, нових - це найбільш заплутане. Експериментуючи, нове ключове слово наче дає розробникам можливість переосмислити реалізацію спадкового класу на реалізацію базового класу, чітко визначивши тип. Це як думати навпаки.

У наведеному нижче прикладі результат буде повертати "Отриманий результат", поки тип не буде чітко визначений як тест BaseClass, лише тоді повернеться "Базовий результат".

class Program
{
    static void Main(string[] args)
    {
        var test = new DerivedClass();
        var result = test.DoSomething();
    }
}

class BaseClass
{
    public virtual string DoSomething()
    {
        return "Base result";
    }
}

class DerivedClass : BaseClass
{
    public new string DoSomething()
    {
        return "Derived result";
    }
}

3
Додайте свій коментар, якщо ви заперечуєте. Бити і бігати так боягузливо.
кориснийБег

0

Функціональна різниця не виявиться в цих тестах:

BaseClass bc = new BaseClass();

bc.DoIt();

DerivedClass dc = new DerivedClass();

dc.ShowIt();

У цьому прикладі виклик Doit - це той, кого ви очікуєте, щоб його покликали.

Щоб побачити різницю, вам потрібно зробити це:

BaseClass obj = new DerivedClass();

obj.DoIt();

Ви побачите , якщо ви запустите цей тест , що в разі 1 (як ви визначили його), то DoIt()в BaseClassназивається, в разі 2 (як ви визначили його), то DoIt()в DerivedClassназивається.


-1

У першому випадку він викличе похідний метод DoIt (), оскільки нове ключове слово приховує метод базового класу DoIt ().

У другому випадку він викличе переотриманий DoIt ()

  public class A
{
    public virtual void DoIt()
    {
        Console.WriteLine("A::DoIt()");
    }
}

public class B : A
{
    new public void DoIt()
    {
        Console.WriteLine("B::DoIt()");
    }
}

public class C : A
{
    public override void DoIt()
    {
        Console.WriteLine("C::DoIt()");
    }
}

нехай створить екземпляр цих класів

   A instanceA = new A();

    B instanceB = new B();
    C instanceC = new C();

    instanceA.DoIt(); //A::DoIt()
    instanceB.DoIt(); //B::DoIt()
    instanceC.DoIt(); //B::DoIt()

Все очікується вище. Дозвольте встановити instanceB і instanceC для instanceA і викликати метод DoIt () і перевірити результат.

    instanceA = instanceB;
    instanceA.DoIt(); //A::DoIt() calls DoIt method in class A

    instanceA = instanceC;
    instanceA.DoIt();//C::DoIt() calls DoIt method in class C because it was overriden in class C

instanceC.DoIt (); дасть вам C :: DoIt (), а не B :: DoIt ()
BYS2

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.