2012-06-14 10 views
18

Quando ereditare una classe ereditata, il comportamento nuova/esclusione non è quello che ci si aspetterebbe:Qual è il caso d'uso di questa idiosincrasia ereditaria?

$ cat Program.cs 
using System; 

class A { 
    public virtual void SayHi() { 
     Console.WriteLine("From A"); 
    } 
} 
class B : A { 
    public new virtual void SayHi() { 
     Console.WriteLine("From B"); 
    } 
} 
class C : B { 
    public override void SayHi() { 
     Console.WriteLine("From C"); 
    } 
} 

public class Program { 
    public static void Main() { 
     A p = new C(); 
     p.SayHi(); 
    } 
} 

$ ./Program.exe 
From A 

come Classe C ridefinisce il metodo sayHi() mi aspetterei l'uscita di essere From C. Perché il modificatore new della classe B ha la precedenza qui? Qual è il caso d'uso per questo? Soprattutto perché rompe l'ovvio caso d'uso di avere C davvero prioritario A.

Si noti che il codice sopra è stato eseguito su Mono 2.10 in esecuzione su una distro derivata da Debian. Ma ho confermato lo stesso comportamento usando il compilatore C# in MS Visual Studio.

+2

'new' da' B' si nasconde il sovraccarico virtuale in 'C'. Cambia 'p' per digitare' B' e vedrai l'output da 'C' – asawyer

+0

La domanda è" a che serve questo? " ma la vera domanda è "perché fa questo?". Sono curioso di sapere che il caso d'uso per questo è: sconfiggere intenzionalmente l'ereditarietà in un modo non ovvio per i consumatori della tua API sembra incredibilmente pericoloso. – Chuu

+0

@ Chuu: Grazie, ho chiarito. – dotancohen

risposta

21

Il new modifier causa l'occultamento del membro, che interrompe la relazione polimorfica nella gerarchia di classi. Il metodo SayHi di B viene considerato come distinto (non un override) da A's (quindi la scelta della parola "nuovo" come parola chiave). Il metodo C sostituisce quindi lo B e non lo A (che rimane nascosto).

Pertanto, quando si chiama SayHi in un'istanza C attraverso un riferimento A, il runtime risolverebbe contro il tipo A, non il tipo C (entro cui SayHi è un metodo “nuovo” ereditato da B).

Se, d'altra parte, si dovesse eseguire:

B p = new C(); 
p.SayHi(); 

... si otterrebbe il risultato polimorfico atteso:

From C 

Edit: Poiché è stata richiesta un caso d'uso ecco uno. Prima dell'introduzione dei generici in .NET Framework 2.0, l'occultamento dei membri veniva talvolta usato come mezzo per alterare i tipi di ritorno dei metodi ereditati nelle classi derivate (cosa che non è possibile fare quando si esegue l'override) per restituire tipi più specifici. Per esempio:

class ObjectContainer 
{ 
    private object item; 

    public object Item 
    { 
     get { return item; } 
     set { item = value; } 
    } 
} 

class StringContainer : ObjectContainer 
{ 
    public new virtual string Item 
    { 
     get { return base.Item as string; } 
     set { base.Item = value as string; } 
    } 
} 

class QuotedStringContainer : StringContainer 
{ 
    public override string Item 
    { 
     get { return "\"" + base.Item + "\""; } 
    } 
} 

La Item proprietà della classe ObjectContainer restituisce una pianura object. Tuttavia, in StringContainer, questa proprietà ereditata è nascosta per restituire invece string. Così:

ObjectContainer oc = new StringContainer(); 
object o = oc.Item; // Valid, since ObjectContainer.Item is resolved 
string s1 = oc.Item; // Not valid, since ObjectContainer.Item is still resolved 
string s2 = ((StringContainer)oc).Item; 
         // Valid, since StringContainer.Item is now resolved 

La classe QuotedStringContainer ignora la proprietà di StringContainer, ereditando il suo tipo di ritorno stringItem; tuttavia, è ancora nascosto dallo object -ritorno a Item proprietà di ObjectContainer. Se non fosse così, non ci sarebbe modo di conciliare i loro tipi restituiti disparati ...

ObjectContainer oc = new QuotedStringContainer(); 
object o = oc.Item; // Valid, since ObjectContainer.Item is resolved 
string s1 = oc.Item; // Not valid, since ObjectContainer.Item is still resolved 
string s2 = ((StringContainer)oc).Item; 
         // Valid, since QuotedStringContainer.Item is now resolved 
         // (polymorphism!) 
string s3 = ((QuotedStringContainer)oc).Item; 
         // Valid, since QuotedStringContainer.Item is now resolved 
+0

Grazie. Capisco in che modo B cambierebbe il metodo di C, ma c'è un caso d'uso per rompere la relazione polimorfica tra A e C? Il ragazzo che programma C potrebbe avere buone ragioni per sovrascrivere il metodo di A, ma non riesco a vedere il caso d'uso per prevenirlo, non importa se B è definito come 'nuovo' o' sovrascrivo '. – dotancohen

+1

Poiché 'C' deriva da' B' che deriva da 'A', non esiste una relazione polimorfica diretta tra' C' e 'A', ma solo una indiretta tramite' B'. Se la catena 'B'-' A' è rotta, allora 'C' non ha più pretesa di' A', dal momento che 'B' ha rotto il canale – Douglas

+0

, a quanto vedo, questo ha senso. Grazie Douglas. – dotancohen

7

C è prevalente la versione shadowed del metodo (che è l'essere shadowed in B) e non sovrascrivendo quello in A .

Come risultato, quando si utilizza una variabile di tipo A, il SayHi definito A è chiamato, come si non viene sovrascritta in C.

6

C esegue l'override del metodo B, quindi quando lo trasmetti a A, si termina chiamando il virtuale definito in A.

Vedere ECMA 334: C# Language Specification in 17.5.3 è il vostro esempio (pagina 294).

+0

Grazie per il link e la menzione della pagina. Tuttavia, non vedo ancora un caso d'uso per questo comportamento, mentre il caso d'uso di sovrascrivere A da C indipendentemente da B è abbastanza ovvio. – dotancohen

2

Poiché la classe C non sovrascrive il metodo SayHi nella classe A, sovrascrive il metodo "nuovo" in B. Poiché il cast è su A, il compilatore lo risolve come una chiamata a A.SayHi() piuttosto che C.SayHi()

2

L'ultimo example da questa pagina di msdn spiega da vicino cosa sta succedendo qui.

Fondamentalmente il nuovo modificatore fa in modo che il metodo in A sia nascosto da C, e poiché è pubblico quando C esegue l'override sovrascrive il metodo da B (che è trattato come un metodo distinto).

Se si imposta il metodo in B a privati, C sarebbe ancora una volta l'override del metodo in A.

class B : A 
{ 
    private new void SayHi() 
    { 
     Console.WriteLine("From B"); 
    } 
} 

risultati in: From C

+0

Grazie, ma quell'esempio si riferisce a metodi privati. – dotancohen

+0

@dotancohen Sì, perché rendere il metodo in 'B' privato espone il metodo' A' sottostante a 'C' per essere sovrascritto, dando il risultato previsto. Dato che hai un metodo pubblico 'B', è quello che viene sovrascritto. – NominSim

Problemi correlati