2015-06-30 14 views
7

Il programma di seguito produce questo output:Perché è un metodo generico scelto quando esiste un non generico?

Foo<T> called 

Process is terminated due to StackOverflowException. 

Quindi, Foo(baz) chiama il generico Foo<T>, ma Bar(baz) recurses e fa non chiamata Bar<T>.

Sono su C# 5.0 e Microsoft .NET. Il compilatore sembra scegliere il metodo generico, invece della ricorsione, quando il metodo non generico è un override.

Dove posso trovare una spiegazione per questa regola? (avevo intuito che il compilatore avrebbe scelto la ricorsione in entrambi i casi.)

Ecco il programma nella sua interezza:

using System; 

namespace ConsoleApplication1 { 
    class Baz { } 

    abstract class Parent { 
     public abstract void Foo(Baz baz); 
    } 

    class Child : Parent { 
     void Bar<T>(T baz) { 
      Console.WriteLine("Bar<T> called"); 
     } 

     public void Bar(Baz baz) { 
      Bar(baz); 
     } 

     void Foo<T>(T baz) { 
      Console.WriteLine("Foo<T> called"); 
     } 

     public override void Foo(Baz baz) { 
      Foo(baz); 
     } 
    } 

    class Program { 
     static void Main(string[] args) { 
      var child = new Child(); 
      child.Foo(null); 
      child.Bar(null); 
      Console.ReadLine(); 
     } 
    } 
} 
+0

Mi piace visualizzare tutte queste situazioni come il modo in cui i creatori del compilatore ti puniscono per l'utilizzo dell'ereditarietà ... –

+0

Sembra che venga data priorità al metodo non sottoposto a override nella classe figlia rispetto al metodo sottoposto a override in la classe del bambino; può trattare il metodo sottoposto a override come parte del genitore. – adamdc78

risposta

5

Secondo la documentazione MSDN, la priorità è data alle firme di metodo che non sono ignorato. Poiché la versione non generica di Foo viene ignorata, si passa immediatamente in fondo alla priorità di scelta di un metodo. In termini generali, il prossimo passo è scegliere il metodo più specifico possibile ed eseguirlo. Nel caso dei metodi Bar, il metodo Bar(Baz baz) sarà sempre il più specifico nel tuo caso.

risoluzione di sovraccarico è un meccanismo di compilazione tempo per la selezione del miglior membro funzione da richiamare proposta una lista di argomenti e un insieme di membri funzione candidato. la risoluzione di sovraccarico seleziona l'elemento funzione invocare nei seguenti contesti distinti all'interno C#:

  • invocazione di un metodo denominato in un'invocazione-espressione (Sezione 7.5.5). Invocazione di un costruttore di istanze denominato in un'espressione di creazione dell'oggetto (Sezione 7.5.10.1).
  • Invocazione di un accessore dell'indicizzatore tramite un accesso agli elementi (Sezione 7.5.6). Invocazione di un operatore predefinito o definito dall'utente referenziato in un'espressione (Sezione 7.2.3 e Sezione 7.2.4).

Ciascuno di questi contesti definisce il serie di elementi funzionali candidati e l'elenco di argomenti a suo modo unico, come descritto in dettaglio nelle sezioni di cui sopra. Per l'esempio , il set di candidati per un richiamo del metodo non include include metodi contrassegnati con override (Sezione 7.3) e i metodi in una classe base non sono candidati se qualsiasi metodo in una classe derivata è applicabile (Sezione 7.5.5.1).

MSDN Overload Resolution

io in grassetto il testo che penso che si riferisce alla tua domanda.

Ecco un'altra domanda su Stack Overflow che potrebbe aiutare. Parla di risoluzione dei metodi in generale. Non tocca i metodi sottoposti a override, ma aiuta a compilare parte del processo che non ho toccato.

+0

bello, senza guardare msdn ho capito il motivo! – Fredou

0

forse si comportano come quando si implementa qualcosa di simile

void myMethod(long? l) { } 
    void myMethod(int? i) { } 

chiamandolo con null utilizzerà il int?

aggiungendo questo

void myMethod(short? i) { } 

ed ancora chiamandolo con null, il codice passerà a short?

forse c'è un ordine interno/priorità in corso?

ora con il vostro codice, sto dando questo solo per mostrare la differenza tra lasciare che il compilatore decidere e il programmatore decide (chiamata esplicita)

questo è il generico Baz

.method private hidebysig 
    instance void Bar<T> (
     !!T baz 
    ) cil managed 
{ 
    // Method begins at RVA 0x2060 
    // Code size 13 (0xd) 
    .maxstack 8 

    IL_0000: nop 
    IL_0001: ldstr "Bar<T> called" 
    IL_0006: call void [mscorlib]System.Console::WriteLine(string) 
    IL_000b: nop 
    IL_000c: ret 
} // end of method Child::Bar 

l'implementazione

public void Bar(Baz baz) { 
     Bar(baz); 
    } 

dare a questo

.method public hidebysig 
    instance void Bar (
     class ConsoleApplication1.Baz baz 
    ) cil managed 
{ 
    // Method begins at RVA 0x206e 
    // Code size 10 (0xa) 
    .maxstack 8 

    IL_0000: nop 
    IL_0001: ldarg.0 
    IL_0002: ldarg.1 
    IL_0003: call instance void ConsoleApplication1.Child::Bar(class ConsoleApplication1.Baz) 
    IL_0008: nop 
    IL_0009: ret 
} // end of method Child::Bar 

questo

public void Bar(Baz baz) 
    { 
     Bar<Baz>(baz); 
    } 

danno presente risoluzione sovraccarico

.method public hidebysig 
    instance void Bar (
     class ConsoleApplication1.Baz baz 
    ) cil managed 
{ 
    // Method begins at RVA 0x206e 
    // Code size 10 (0xa) 
    .maxstack 8 

    IL_0000: nop 
    IL_0001: ldarg.0 
    IL_0002: ldarg.1 
    IL_0003: call instance void ConsoleApplication1.Child::Bar<class ConsoleApplication1.Baz>(!!0) 
    IL_0008: nop 
    IL_0009: ret 
} // end of method Child::Bar 
1

cerca le catene di successione, alla ricerca di metodi definiti in ogni punto.

Child definisce void Foo<T>(T baz) ma non definisce void Foo(Baz baz) in modo da scegliere void Foo<T>(T baz).

Generalmente questo ha senso; in codice reale se Foo<T>(T baz) non ha fatto un lavoro molto simile a quello che ha fatto il Foo(Baz baz) nella base quando ha passato un Baz, quindi il tuo disegno è confuso e dovresti scegliere un nuovo nome.

È possibile kludge cose un po 'con l'utilizzo di public new void Foo(Baz baz) o public new virtual void Foo(Baz baz) per forzare un override da definire in Child troppo (anche se qui non ci sarebbe bisogno di essere un passo intermedio nella gerarchia in modo che il metodo astratto è un'implementazione), che Potremmo chiamare base.Foo(baz) (per chiamare in un'implementazione di base) e/o Foo<Baz>(baz) (per chiamare nella versione generica) `, ma è meglio evitare tali trucchi.

Problemi correlati