2015-07-10 13 views
11

Ho la seguente classe di estensioni:I metodi di estensione per le interfacce sono considerati meno prioritari rispetto a quelli meno specifici?

public static class MatcherExtensions 
{ 
    public static ExecMatcher<T1, T2> Match<T1, T2>(this Tuple<T1, T2> item) 
    { 
     return new ExecMatcher<T1, T2>(item.Item1, item.Item2); 
    } 

    public static ExecMatcher<T1, T2> Match<T1, T2>(this ITupleMatchable<T1, T2> item) 
    { 
     var tuple = item.PropertiesToMatch; 
     return new ExecMatcher<T1, T2>(tuple.Item1, tuple.Item2); 
    } 

    public static ExecMatcher<T> Match<T>(this T item) { return new ExecMatcher<T>(item); } 
} 

Se creo una tupla e invoco Match(), utilizza correttamente la prima metodo di estensione:

var tuple = Tuple.Create(1, "a"); 
tuple.Match().With(1, "a")... // compiles just fine. 

Se creo un int e invoco Match(), si utilizza correttamente l'ultimo metodo di estensione:

var one = 1; 
one.Match().With(1)... // compiles just fine. 

Tuttavia se creo SomeClass, che implementa ITupleMatchable<int, string> e cercare di abbinare su di esso, il compilatore sceglie ancora il terzo metodo di estensione, piuttosto che la seconda, pur essendo quest'ultimo una più specifica partita:

var obj = new SomeClass(1, "a"); 
obj.Match().With(1, "a")... // won't compile. 

Sulla base Eric Lippert's answer to a similar question, ho lavorato attorno a questo mettendo il terzo metodo di estensione nella propria classe all'interno di una sottodirectory, creando così uno spazio dei nomi più lungo e quindi più "distanza" tra esso e il codice chiamante rispetto alla versione specifica a ITupleMatchable<T1, T2>. Questo mi sembra un trucco per me. C'è un modo più ordinato per risolvere questo?

+1

'ITupleMatchable obj = new SomeClass (1," a ");' dovrebbe funzionare, non dovrebbe? –

+1

Se la tua domanda è principalmente quella nel titolo della tua domanda, allora penso che il post di Eric Lippert che hai collegato risponda in modo abbastanza conclusivo, quindi questa domanda è davvero un duplicato di quella.Se stai cercando soluzioni alternative, prova a cambiare la domanda nel titolo per riflettere questo. – sstan

+0

@sstan, la risposta di Eric spiega come risolvere il problema (come spiegato nella mia domanda). Non affronta la mia domanda per quanto riguarda il motivo per cui una firma meno specifica viene scelta su una più specifica (per quanto posso vedere). Mi piacerebbe che tu mi correggessi, sottolineando ciò che mi manca :) –

risposta

3

Se avete semplicemente lanci new SomeClass(1, "a") a ITupleMatchable<int, string>, funzionerà bene:

var obj = (ITupleMatchable<int, string>)new SomeClass(1, "a"); 
obj.Match().With(1, "a"); 

Ricordate che il vostro variabile obj ha altrimenti un tipo in fase di compilazione della SomeClass. Il compilatore può "più facilmente" abbinare la classe reale al terzo metodo di estensione (che è compatibile con il tipo) di quello qualsiasi, dovendo osservare le implementazioni dell'interfaccia di SomeClass e quindi farlo corrispondere ad es. il secondo metodo di estensione.

Ma se si fornisce il parametro this come tipo di interfaccia effettivo, il secondo metodo di estensione è più adatto, poiché è esattamente il tipo che il metodo sta cercando, piuttosto che il più ampio "qualsiasi tipo". Cioè è una dichiarazione più specifica, e quindi è "migliore".

Si noti che una volta trovato il candidato insieme di metodi di estensione (tramite regole relative al namespace, ecc.), Il metodo effettivo viene determinato utilizzando la normale risoluzione di sovraccarico. Cioè avendo determinato che almeno un metodo nella tua classe MatcherExtensions è un metodo di estensione idoneo, il compilatore quindi accetta le normali regole di risoluzione di sovraccarico per selezionarle. È possibile trovare tali regole nella specifica C# 5.0, sezione 7.5.3.

Brevemente però: prima di applicare le regole di risoluzione di sovraccarico (in effetti, al fine di determinare quali metodi sono anche ammissibili), si noti che il compilatore ha già deciso i parametri di tipo generico. Quindi, mentre valuta la risoluzione di sovraccarico, sta guardando Match(SomeClass item) e Match(ITupleMatchable<int, string> item). Speriamo che una volta preso in considerazione, vedrete perché, se la variabile ha il tipo SomeClass, il compilatore seleziona la terza estensione preferibilmente nel secondo e viceversa se il tipo è ITupleMatchable<int, string>.

+0

Quindi, in breve, la risposta alla mia domanda è "sì" :) Non voglio forzare gli utenti a dover eseguire il cast sul tipo di interfaccia, quindi mi limiterò alla soluzione "a distanza" di Eric. Molte grazie per una risposta così ben spiegata e dettagliata. –