Różnica między nowym a zastąpieniem


198

Zastanawiasz się, jaka jest różnica między następującymi:

Przypadek 1: klasa podstawowa

public void DoIt();

Przypadek 1: Dziedziczona klasa

public new void DoIt();

Przypadek 2: klasa podstawowa

public virtual void DoIt();

Przypadek 2: Dziedziczona klasa

public override void DoIt();

Zarówno przypadek 1, jak i 2 wydają się mieć ten sam efekt na podstawie testów, które przeprowadziłem. Czy jest jakaś różnica lub preferowany sposób?


2
Duplikat wielu pytań, w tym stackoverflow.com/questions/159978/…
Jon Skeet

Odpowiedzi:


267

Modyfikator zastępowania może być stosowany w metodach wirtualnych i musi być stosowany w metodach abstrakcyjnych. Oznacza to, że kompilator ma użyć ostatniej zdefiniowanej implementacji metody. Nawet jeśli metoda zostanie wywołana w odniesieniu do klasy bazowej, użyje implementacji zastępującej ją.

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

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

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

zadzwoni, Derived.DoItjeśli to zastąpi Base.DoIt.

Nowy modyfikator instruuje kompilator, aby używał implementacji klasy podrzędnej zamiast implementacji klasy nadrzędnej. Każdy kod, który nie odwołuje się do twojej klasy, ale klasa nadrzędna będzie używał implementacji klasy nadrzędnej.

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

Najpierw zadzwonić Base.DoIt, wtedy Derived.DoIt. W rzeczywistości są to dwie całkowicie odrębne metody, które mają tę samą nazwę, a nie metoda pochodna zastępująca metodę podstawową.

Źródło: blog Microsoft


5
This indicates for the compiler to use the last defined implementation of a method. jak znaleźć ostatnią zdefiniowaną implementację metody?
AminM

5
Zacznij od konkretnej klasy, sprawdź, czy ma implementację interesującej metody. Jeśli tak, to koniec. Jeśli nie, przejdź o jeden krok wyżej w hierarchii dziedziczenia, tj. Sprawdź, czy superklasa ma odpowiednią metodę. Kontynuuj, aż znajdziesz interesującą metodę.
csoltenborn

2
Zauważ też, że możesz overridemetodę tylko wtedy, gdy klasa podstawowa zdefiniuje metodę jako virtual. Słowo virtualjest klasą bazową mówiąc: „Hej, kiedy wywołanie tej metody, to mógłby praktycznie zostały zastąpione realizacji pochodnej, więc naprawdę nie wiem z góry, jaka metoda realizacja ja faktycznie dzwoni w czasie wykonywania. Więc virtualoznacza to symbol zastępczy metody. Oznacza to, że metod, które nie są oznaczone jako virtualnie można zastąpić, ale można zastąpić dowolną nie-wirtualną metodę w klasie pochodnej modyfikatorem new, dostępnym tylko na poziomie pochodnym.
Erik Bongers

177

virtual : wskazuje, że dziedziczenie może zastąpić metodę

override : zastępuje funkcjonalność metody wirtualnej w klasie bazowej, zapewniając inną funkcjonalność.

nowość : ukrywa oryginalną metodę (która nie musi być wirtualna), zapewniając inną funkcjonalność. Należy tego używać tylko wtedy, gdy jest to absolutnie konieczne.

Po ukryciu metody nadal można uzyskać dostęp do oryginalnej metody, rzutując w górę na klasę podstawową. Jest to przydatne w niektórych scenariuszach, ale niebezpieczne.


2
Dlaczego używanie metody ukrywania metody podstawowej jest niebezpieczne? A może sugerujesz, że casting w ogóle jest niebezpieczny?
Mark

3
@ Mark - osoba dzwoniąca może nie być świadoma implementacji, powodując przypadkowe niewłaściwe użycie.
Jon B

Czy możesz używać metody rodzicielskiej overridei / lub newbez virtualniej?
Aaron Franke,

16

W pierwszym przypadku ukrywasz definicję w klasie nadrzędnej. Oznacza to, że zostanie on wywołany tylko wtedy, gdy będziesz miał do czynienia z obiektem jako klasą potomną. Jeśli rzutujesz klasę na jej typ nadrzędny, metoda rodzica zostanie wywołana. W drugim przypadku metoda jest nadpisywana i będzie wywoływana niezależnie od tego, czy obiekt jest rzutowany jako klasa potomna czy nadrzędna.


7

spróbuj wykonać następujące czynności: (case1)

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

Edycja: wirtualny + przesłonięcie jest rozwiązywane w czasie wykonywania (więc przesłonięcie naprawdę zastępuje metody wirtualne), podczas gdy nowy po prostu utwórz nową metodę o tej samej nazwie i ukrywa stary, jest on rozwiązywany w czasie kompilacji -> Twój kompilator wywoła metodę „ widzi „


3

W przypadku 1, jeśli użyłeś metody DoIt () odziedziczonej klasy, podczas gdy typ jest zadeklarowany jako klasa podstawowa, zobaczysz nawet akcję klasy podstawowej.

/* 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();
}

Czy możesz opublikować ostrzeżenie lub błąd, który otrzymujesz? Ten kod działał dobrze, kiedy go pierwotnie opublikowałem.
Matthew Whited

Wszystko to należy wkleić w klasie punktu wejścia (Program). Zostało to usunięte, aby umożliwić lepsze formatowanie na tej stronie.
Matthew Whited

3

Różnica między tymi dwoma przypadkami polega na tym, że w przypadku 1 DoItmetoda podstawowa nie jest zastępowana, tylko ukryta. Oznacza to, że w zależności od typu zmiennej zależy, która metoda zostanie wywołana. Na przykład:

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

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

Może to być bardzo mylące i skutkuje nieoczekiwanym zachowaniem, którego należy unikać, jeśli to możliwe. Tak więc preferowanym sposobem byłby przypadek 2.


3
  • newoznacza uszanowanie typu ODNIESIENIA (po lewej stronie =), tym samym uruchamiając metodę typów odniesienia. Jeśli przedefiniowana metoda nie ma newsłowa kluczowego, zachowuje się tak, jak ma. Co więcej, jest również znany jako niepolimorficzne dziedziczenie . To znaczy: „Tworzę zupełnie nową metodę w klasie pochodnej, która absolutnie nie ma nic wspólnego z żadnymi metodami o tej samej nazwie w klasie bazowej”. - powiedział Whitaker
  • override, który musi być używany ze virtualsłowem kluczowym w swojej klasie bazowej, oznacza respektowanie typu OBJECT (po prawej stronie =), dzięki czemu można zastąpić metodę niezależnie od typu odwołania. Co więcej, jest również znany jako dziedziczenie polimorficzne .

Chciałbym pamiętać, że oba słowa kluczowe są sobie przeciwne.

override: virtualnależy zdefiniować słowo kluczowe, aby zastąpić metodę. Metoda wykorzystująca overridesłowo kluczowe, które niezależnie od typu odwołania (odwołanie do klasy bazowej lub pochodnej), jeśli jest tworzone z klasą bazową, działa metoda klasy bazowej. W przeciwnym razie działa metoda klasy pochodnej.

new: jeśli słowo kluczowe jest używane przez metodę, w przeciwieństwie do overridesłowa kluczowego, typ odniesienia jest ważny. Jeśli zostanie utworzona instancja z klasą pochodną, ​​a typem referencyjnym jest klasa bazowa, uruchomiona zostanie metoda klasy bazowej. Jeśli zostanie utworzona instancja z klasą pochodną, ​​a typem referencyjnym jest klasa pochodna, zostanie uruchomiona metoda klasy pochodnej. Mianowicie jest to kontrast overridesłowa kluczowego. En passant, jeśli zapomnisz lub pominiesz dodanie nowego słowa kluczowego do metody, kompilator zachowuje się domyślnie, gdy newużywane jest słowo kluczowe.

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";
    }
}

Zadzwoń w głównej części:

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());

Wynik:

A
B
B
base test
derived test

Przykład nowego kodu,

Graj kodem, komentując jeden po drugim.

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

Jeśli słowo kluczowe overridejest używane w klasie pochodnej, to zastępuje metodę nadrzędną.

Jeśli słowo kluczowe newjest używane w klasie wyprowadzającej, wyprowadza metodę ukrytą metodą nadrzędną.


1

Miałem to samo pytanie i to jest bardzo mylące, należy wziąć pod uwagę to zastąpienie i nowe słowa kluczowe działające tylko z obiektami klasy bazowej typu i wartością klasy pochodnej. W tym przypadku tylko zobaczysz efekt ręcznego i nowy: Więc jeśli masz class Ai B, Bdziedziczy A, wtedy instancję obiektu tak:

A a = new B();

Teraz przy wywoływaniu metody wezmą pod uwagę jego stan. Przesłonięcie : oznacza, że ​​rozszerza funkcję metody, następnie używa metody w klasie pochodnej, podczas gdy nowy nakazuje kompilatorowi, aby ukrył metodę w klasie pochodnej i zamiast tego używa metody w klasie bazowej. Oto bardzo dobry widok na ten temat:

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


1

Poniższy artykuł znajduje się na stronie vb.net, ale myślę, że wyjaśnienie na temat zastąpienia nowego vs zastąpienia jest bardzo łatwe do zrozumienia.

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

W pewnym momencie tego artykułu znajduje się zdanie:

Ogólnie rzecz biorąc, Shadows zakłada, że ​​wywoływana jest funkcja powiązana z typem, a Overrides zakłada, że ​​implementacja obiektu jest wykonywana.

Przyjęta odpowiedź na to pytanie jest idealna, ale myślę, że ten artykuł zawiera dobre przykłady, które mogą nadać lepszy sens różnicom między tymi dwoma słowami kluczowymi.


1

Spośród tych wszystkich nowość jest najbardziej zagmatwana. Poprzez eksperymentowanie nowe słowo kluczowe przypomina programistom opcję zastąpienia implementacji klasy dziedziczącej implementacją klasy podstawowej poprzez jawne zdefiniowanie typu. To jest jak myślenie na odwrót.

W poniższym przykładzie wynik zwróci „Wynik pochodny”, dopóki typ nie zostanie jawnie zdefiniowany jako test BaseClass, tylko wtedy zostanie zwrócony „Wynik podstawowy”.

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
Dodaj swój komentarz, jeśli wyrazisz sprzeciw. Uderzenie i ucieczka są tak tchórzliwe.
Przydatne,

0

Różnica funkcjonalna nie zostanie pokazana w tych testach:

BaseClass bc = new BaseClass();

bc.DoIt();

DerivedClass dc = new DerivedClass();

dc.ShowIt();

W tym przykładzie wywoływana jest Doit, której się spodziewasz.

Aby zobaczyć różnicę, musisz to zrobić:

BaseClass obj = new DerivedClass();

obj.DoIt();

Zobaczysz, jeśli uruchomisz ten test, że w przypadku 1 (tak jak go zdefiniowałeś), DoIt() w BaseClassnazywa, w przypadku 2 (zgodnie z definicją), przy czym DoIt()w DerivedClassnazywa.


-1

W pierwszym przypadku wywoła metodę pochodnej klasy DoIt (), ponieważ nowe słowo kluczowe ukrywa metodę klasy bazowej DoIt ().

W drugim przypadku wywoła przesłoniętą funkcję 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()");
    }
}

pozwól stworzyć instancję tych klas

   A instanceA = new A();

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

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

Wszystko oczekuje się powyżej. Pozwól ustawić instanceB i instanceC na instanceA i wywołaj metodę DoIt () i sprawdź wynik.

    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 (); dałby Ci C :: DoIt (), a nie B :: DoIt ()
BYS2

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.