C # – Uso di parole chiave virtuale + override contro nuovo

Quali sono le differenze tra dichiarare un metodo in un tipo di base ” virtual ” e sovrascriverlo in un tipo di figlio utilizzando la parola chiave ” override ” anziché utilizzare semplicemente la parola chiave ” new ” quando si dichiara il metodo di corrispondenza nel tipo child?

La “nuova” parola chiave non sovrascrive, indica un nuovo metodo che non ha nulla a che fare con il metodo della class base.

 public class Foo { public bool DoSomething() { return false; } } public class Bar : Foo { public new bool DoSomething() { return true; } } public class Test { public static void Main () { Foo test = new Bar (); Console.WriteLine (test.DoSomething ()); } } 

Questo stampa false, se hai usato l’override, sarebbe stato stampato true.

(Codice base preso da Joseph Daigle)

Quindi, se stai facendo un vero polimorfismo, DEVI SEMPRE ESEGUIRE . L’unico posto in cui è necessario utilizzare “nuovo” è quando il metodo non è correlato in alcun modo alla versione della class base.

Trovo sempre cose come questa più facilmente comprensibili con le immagini:

Di nuovo, prendendo il codice di joseph daigle,

 public class Foo { public /*virtual*/ bool DoSomething() { return false; } } public class Bar : Foo { public /*override or new*/ bool DoSomething() { return true; } } 

Se quindi chiami il codice in questo modo:

 Foo a = new Bar(); a.DoSomething(); 

NOTA: La cosa importante è che il nostro object è in realtà una Bar , ma lo stiamo memorizzando in una variabile di tipo Foo (questo è simile al casting)

Quindi il risultato sarà il seguente, a seconda che tu abbia usato virtual / override o new quando dichiari le tue classi.

Spiegazione virtuale / sovrascrittura

Ecco un codice per capire la differenza nel comportamento dei metodi virtuali e non virtuali:

 class A { public void foo() { Console.WriteLine("A::foo()"); } public virtual void bar() { Console.WriteLine("A::bar()"); } } class B : A { public new void foo() { Console.WriteLine("B::foo()"); } public override void bar() { Console.WriteLine("B::bar()"); } } class Program { static int Main(string[] args) { B b = new B(); A a = b; a.foo(); // Prints A::foo b.foo(); // Prints B::foo a.bar(); // Prints B::bar b.bar(); // Prints B::bar return 0; } } 

La new parola chiave crea effettivamente un membro completamente nuovo che esiste solo su quel tipo specifico.

Per esempio

 public class Foo { public bool DoSomething() { return false; } } public class Bar : Foo { public new bool DoSomething() { return true; } } 

Il metodo esiste su entrambi i tipi. Quando si utilizza il reflection e si ottengono i membri di tipo Bar , in realtà verranno individuati 2 metodi chiamati DoSomething() che hanno lo stesso aspetto. Usando il new si nasconde efficacemente l’implementazione nella class base, in modo che quando le classi derivano da Bar (nel mio esempio) la chiamata del metodo a base.DoSomething() passi a Bar e non a Foo .

virtual / override dice al compilatore che i due metodi sono correlati e che in alcune circostanze, quando penseresti di chiamare il primo metodo (virtuale), in realtà è corretto chiamare invece il secondo metodo (sovrascritto). Questo è il fondamento del polimorfismo.

 (new SubClass() as BaseClass).VirtualFoo() 

Chiamerà il metodo VirtualFoo () sovrascritto della Sottoclass.

new dice al compilatore che stai aggiungendo un metodo a una class derivata con lo stesso nome di un metodo nella class base, ma non hanno alcuna relazione tra loro.

 (new SubClass() as BaseClass).NewBar() 

Chiamerà il metodo NewBar () di BaseClass, mentre:

 (new SubClass()).NewBar() 

Chiamerà il metodo NewBar () della Sottoclass.

Oltre ai dettagli tecnici, penso che l’uso di virtual / override comunichi molte informazioni semantiche sul design. Quando si dichiara un metodo virtuale, si indica che ci si aspetta che le classi di implementazione possano fornire le proprie implementazioni non predefinite. Omettere questo in una class base, allo stesso modo, dichiara l’aspettativa che il metodo predefinito dovrebbe essere sufficiente per tutte le classi di implementazione. Allo stesso modo, si possono usare dichiarazioni astratte per forzare l’implementazione delle classi per fornire la propria implementazione. Ancora una volta, penso che questo comunichi molto su come il programmatore si aspetta che il codice venga usato. Se scrivessi sia la base e le classi di implementazione e mi trovassi a usare il nuovo, avrei seriamente ripensato alla decisione di non rendere il metodo virtuale nel genitore e dichiarare specificamente il mio intento.

La differenza tra la parola chiave override e la nuova parola chiave è che il primo sovrascrive il metodo e più tardi nasconde il metodo.

Dai un’occhiata ai link seguenti per maggiori informazioni …

MSDN e altro

  • new parola chiave è per hide. – significa che stai nascondendo il tuo metodo in fase di runtime. L’output sarà basato sul metodo della class base.
  • override per l’override. – significa che stai invocando il tuo metodo di class derivato con il riferimento della class base. L’output sarà basato sul metodo della class derivata.

La mia versione di spiegazione viene dall’uso delle proprietà per aiutare a comprendere le differenze.

override è abbastanza semplice, giusto? Il tipo sottostante sovrascrive quello del genitore.

new è forse il fuorviante (per me lo era). Con le proprietà è più facile capire:

 public class Foo { public bool GetSomething => false; } public class Bar : Foo { public new bool GetSomething => true; } public static void Main(string[] args) { Foo foo = new Bar(); Console.WriteLine(foo.GetSomething); Bar bar = new Bar(); Console.WriteLine(bar.GetSomething); } 

Usando un debugger puoi notare che Foo foo ha 2 proprietà GetSomething , poiché in realtà ha 2 versioni della proprietà, Foo e Bar , e per sapere quale usare, c # “seleziona” la proprietà per il tipo corrente .

Se volessi usare la versione della barra, avresti usato l’override o invece usare Foo foo .

Bar bar ha solo 1 , in quanto vuole un comportamento completamente nuovo per GetSomething .