2010-01-22 12 views
6

Avere una classe che ha un metodo, come questo:Metodi di estensione che sovraccaricano in C#, funziona?

class Window { 
    public void Display(Button button) { 
     // ... 
    } 
} 

è possibile sovraccaricare il metodo con un altro uno che è più ampia, in questo modo:

class WindowExtensions { 
    public void Display(this Window window, object o) { 
     Button button = BlahBlah(o); 
     window.Display(button); 
    } 
} 

Che cosa è successo quando ho provato è che ho una ricorsione infinita. C'è un modo per farlo funzionare? Voglio che il metodo di estensione venga chiamato solo quando non è possibile chiamare l'altro metodo.

+2

Ciò che si ottiene in aggiunta è un'architettura infinitamente pessima;) SCNR – Thorsten79

risposta

0

Non è possibile (vedere anche Monkeypatching For Humans) - forse con DLR e method_missing.

+0

Spiacente, questa risposta non è corretta. È possibile. Vedi le molte altre risposte in questa discussione. – jason

9

Andiamo alle specifiche. In primo luogo, dobbiamo capire le regole per le invocazioni dei metodi. Approssimativamente, inizi con il tipo indicato dall'istanza in cui stai tentando di richiamare un metodo. Si cammina lungo la catena di ereditarietà alla ricerca di un metodo accessibile. Quindi esegui il tuo tipo di inferenza e regole di risoluzione di sovraccarico e invoca il metodo se questo riesce. Solo se non viene trovato alcun metodo, provate a elaborare il metodo come metodo di estensione. Così da §7.5.5.2 (metodo di estensione invocazioni) vedi, in particolare, la dichiarazione in grassetto:

In una chiamata di metodo (§7.5.5.1) di una delle forme

expr.identifier()

expr.identifier(args)

expr.identifier<typeargs>()

expr.identifier<typeargs>(args)

Se la normale elaborazione del richiamo non trova metodi applicabili, viene effettuato un tentativo di elaborare il costrutto come una chiamata al metodo di estensione.

Le regole al di là di questo sono un po 'complicate, ma per il caso semplice che ci hai presentato è piuttosto semplice. Se non esiste un metodo di istanza applicabile, verrà richiamato il metodo di estensione WindowExtensions.Display(Window, object). Il metodo di istanza è applicabile se il parametro su Window.Display è un pulsante o è implicitamente trasferibile a un pulsante. In caso contrario, verrà richiamato il metodo di estensione (poiché tutto ciò che deriva da object è implicitamente applicabile a un object).

Quindi, a meno che non ci sia un pezzo importante che stai lasciando fuori, quello che stai cercando di fare funzionerà.

Così, si consideri il seguente esempio:

class Button { } 
class Window { 
    public void Display(Button button) { 
     Console.WriteLine("Window.Button"); 
    } 
} 

class NotAButtonButCanBeCastedToAButton { 
    public static implicit operator Button(
     NotAButtonButCanBeCastedToAButton nab 
    ) { 
     return new Button(); 
    } 
} 

class NotAButtonButMustBeCastedToAButton { 
    public static explicit operator Button(
     NotAButtonButMustBeCastedToAButton nab 
    ) { 
     return new Button(); 
    } 
} 

static class WindowExtensions { 
    public static void Display(this Window window, object o) { 
     Console.WriteLine("WindowExtensions.Button: {0}", o.ToString()); 
     Button button = BlahBlah(o); 
     window.Display(button); 
    } 
    public static Button BlahBlah(object o) { 
     return new Button(); 
    } 
} 

class Program { 
    static void Main(string[] args) { 
     Window w = new Window(); 
     object o = new object(); 
     w.Display(o); // extension 
     int i = 17; 
     w.Display(i); // extension 
     string s = "Hello, world!"; 
     w.Display(s); // extension 
     Button b = new Button(); 
     w.Display(b); // instance 
     var nab = new NotAButtonButCanBeCastedToAButton(); 
     w.Display(b); // implicit cast so instance 
     var nabexplict = new NotAButtonButMustBeCastedToAButton(); 
     w.Display(nabexplict); // only explicit cast so extension 
     w.Display((Button)nabexplict); // explictly casted so instance 
    } 
} 

Questo stamperà

WindowExtensions.Button: System.Object 
Window.Button 
WindowExtensions.Button: 17 
Window.Button 
WindowExtensions.Button: Hello, world! 
Window.Button 
Window.Button 
Window.Button 
WindowExtensions.Button: NotAButtonButMustBeCastedToAButton 
Window.Button 
Window.Button 

sulla console.

1

Che dovrebbe funzionare, il compilatore sceglierà quasi sempre un metodo di istanza con una firma accettabile su un metodo di estensione con la firma esatta.

Secondo questa article:

Un metodo esempio con un firma accettabile mediante conversione di ampliamento saranno quasi sempre essere preferito un metodo di estensione con una esatta corrispondenza firma. Se il risultato è vincolante per il metodo di istanza quando si utilizza il metodo di estensione , è possibile chiamare in modo esplicito il metodo di estensione utilizzando la convenzione di chiamata del metodo condivisa. Questo è anche il modo di disambiguare due metodi quando nessuno dei due è più specifico.

Sei sicuro di passare un pulsante in modo esplicito?

Oppure void Display(Button button) si chiama in modo ricorsivo?

+2

+1, ma penso che il problema qui sia "quasi sempre preferibile". All'interno di un metodo di estensione preferisce se stesso, con il cast implicito da 'Button' a' object'. Da qui la ricorsione. – Keith

0

Beh, credo che sia un po 'complicato. Se si passa Button come un parametro di metodo:

Button button = BlahBlah(o); 
window.Display(button); 

poi c'è metodo della classe adatta, che ha sempre la precedenza sul metodo di estensione.

Ma se si passa un oggetto che non è Button, non esiste un metodo di classe adatto e verrà richiamato il metodo di estensione.

var o = new object(); 
window.Display(o); 

Quindi, da quello che vedo, il tuo esempio dovrebbe funzionare correttamente e metodo di estensione chiamerà Display metodo nell'istanza Window. Il ciclo infinito potrebbe essere causato da qualche altro codice.

C'è qualche possibilità che la classe Window contenente il metodo Display nell'esempio e la classe Window che è un parametro del metodo di estensione siano in realtà due classi diverse?

+0

Tranne 'window.Display (new Button());' chiamerà effettivamente il metodo di estensione, motivo per cui ottiene la ricorsione infinita. – Keith

+0

@ Keith: No, non dovrebbe; 'window.Display (new Button()) // window is Window' dovrebbe chiamare' Window.Display (Button) '. – jason

+0

@Keith: Hm .. in realtà 'window.Display (new Button())' funziona bene per me - chiama solo il metodo instance. – AlexD

2

È possibile, sebbene sia necessario fare attenzione con i parametri sui sovraccarichi, di solito è una buona idea evitare i tipi object poiché ciò causa spesso codice confuso. Puoi cadere in un modo divertente in cui C# preleva i sovraccarichi. Sceglierà una corrispondenza più "ravvicinata" con tipi che possono essere espressi espressi in un "ulteriore" con corrispondenze esatte (vedere this question).

Button myButton = // get button 
Window currentWindow = // get window 

// which method is called here? 
currentWindow.Display(myButton); 

si desidera che il codice sia abbastanza chiaro, soprattutto quando si ritorna a questo codice in un anno o giù di lì, che cosa sovraccarico viene chiamato.

I metodi di estensione forniscono un modo davvero elegante di espandere la funzionalità degli oggetti. È possibile aggiungere un comportamento che non aveva in origine. Devi stare attento con loro, dato che sono molto inclini a creare codice confuso. È consigliabile evitare i nomi dei metodi già utilizzati, anche se sono sovraccarichi evidenti, poiché non vengono visualizzati dopo la lezione in intellisense.

Qui il problema sembra essere che il metodo di estensione può convertire implicitamente il tuo pulsante in un oggetto, e quindi sceglie se stesso come la migliore corrispondenza, invece del metodo di visualizzazione effettivo. È possibile chiamare esplicitamente il metodo di estensione come una normale chiamata statica, ma non è possibile forzarlo a chiamare il metodo della classe sottostante.

vorrei cambiare il nome del metodo di estensione:

object somethingToMakeIntoAButton = // get object 
Window currentWindow = // get window 

// which method is called here? 
currentWindow.DisplayButton(somethingToMakeIntoAButton); 

Poi ...

class WindowExtensions 
{ 
    public void DisplayButton(this Window window, object o) 
    { 
     Button button = BlahBlah(o); 

     // now it's clear to the C# compiler and human readers 
     // that you want the instance method 
     window.Display(button); 
    } 
} 

In alternativa, se il secondo parametro del metodo di estensione era un tipo che non potrebbe essere implicitamente convertito da Button (ad esempio int o string) questa confusione non si verifica neanche.

+0

Un buon consiglio per cambiare il nome del metodo. Ma se provate a eseguire l'esempio fornito da Jason potete vedere che 'metodo di estensione' non sceglie se stesso come la migliore corrispondenza, ma' compiler' seleziona il metodo di istanza da richiamare dal metodo di estensione. – AlexD

+0

Quindi non dovrebbe ottenere l'errore di ricorsione, ma in entrambi i casi penso che il consiglio sia valido: sovraccarichi in cui i tipi di parametri possono essere derivati ​​l'uno dall'altro sono sempre una cattiva idea. – Keith

Problemi correlati