2013-04-02 14 views
16

Sono consapevole che un modificatore params (che trasforma un parametro del tipo di matrice in un cosiddetto "array di parametri") non fa specificamente parte della firma del metodo. Consideriamo ora questo esempio:Modifica del modificatore param in un metodo prioritario

class Giraffid 
{ 
    public virtual void Eat(int[] leaves) 
    { 
     Console.WriteLine("G"); 
    } 
} 
class Okapi : Giraffid 
{ 
    public override void Eat(params int[] leaves) 
    { 
     Console.WriteLine("O"); 
    } 
} 

Questo compila senza avvisi. Quindi:

var okapi = new Okapi(); 
okapi.Eat(2, 4, 6); // will not compile! 

restituisce un errore (No overload for method 'Eat' takes 3 arguments).

Ora, so che il compilatore traduce il modificatore params in un'applicazione del System.ParamArrayAttribute sul parametro in questione. In generale non c'è alcun problema nell'applicare una serie di attributi a un parametro di un metodo virtuale e quindi decorare il parametro "corrispondente" in un metodo di sovrascrittura in una classe derivata con un diverso insieme di attributi.

Eppure il compilatore sceglie di ignorare la mia parola chiave params in silenzio. Al contrario, se si effettua il contrario e si applica params al parametro nella classe base Giraffid e quindi si omette la parola chiave nell'override in Okapi, il compilatore sceglie di decorare entrambi i metodi con System.ParamArrayAttribute. Ho verificato queste cose con IL DASM, ovviamente.

La mia domanda:

È questo il comportamento documentato? Ho cercato accuratamente attraverso la specifica del linguaggio C# senza trovare alcuna menzione di questo.

Posso dire che almeno l'ambiente di sviluppo di Visual Studio è confuso su questo. Quando si digita il 2, 4, 6 nella chiamata di metodo precedente, lo intellisense mi mostra void Okapi.Eat(params int[] leaves) in un suggerimento.


Per confronto, ho anche provato attuazione di un metodo di interfaccia e cambiando la presenza/assenza di params nell'interfaccia e nell'attuazione classe, e ho provato definente un tipo delegato e cambiando params o non sia la definizione tipo delegato o il metodo il cui gruppo di metodi I ha assegnato a una variabile del mio tipo delegato. In questi casi era perfettamente possibile cambiare params -ness.

risposta

16

Il comportamento del compilatore è corretto, ma questo è un po 'un disastro. Avrei preferito che fosse almeno un avvertimento.

Non è sorprendente che non sia possibile trovare il punto in cui la specifica dice che è corretto. I bit rilevanti sono:

L'elaborazione del tempo di associazione di un richiamo del metodo del modulo M (A), dove M è un gruppo di metodi e A è un elenco di argomenti facoltativo, consiste dei seguenti passaggi: Viene creato il set di metodi candidati per l'invocazione del metodo. Per ogni metodo F associata al gruppo metodo M, se F non è generica, F è un candidato quando M non ha un tipo lista degli argomenti, e F è applicabile con riferimento A.

Quali sono i "metodi associato al gruppo di metodi M "? Bene, prima di tutto, cos'è un gruppo di metodi?

Un gruppo metodo, che è un insieme di metodi di overload risultanti da una ricerca membro ...

OK, quindi quali sono le regole di ricerca utente?

In caso contrario, il set è costituito da tutti i membri accessibili denominati N in T, inclusi i membri ereditati ei membri accessibili denominati N nell'oggetto. I membri che includono un modificatore di sostituzione sono esclusi dal set.

Enfasi aggiunta.

Il risultato pratico è che a scopo di risoluzione di sovraccarico, un metodo sovrascritto è considerato il metodo che è stato originariamente dichiarato , non il metodo che è stato override. Questa regola è, purtroppo, violate in questo caso:

virtual void M(int x, int y) { } 
... 
override void M(int y, int x) { } 
... 
M(x = 1, y = 2); 

risoluzione sovraccarico utilizza i nomi dal più derivato versione. Questa è una sfortunata conseguenza del fatto che gli argomenti nominati sono stati aggiunti molto tardi nel gioco.

In breve: ai fini di stabilire se un metodo è "params" o no, l'analisi viene fatta sul metodo originale, non sulla metodo ridefinito.

Sarebbe stato bello se il compilatore ti avesse dato un avvertimento qui.

può dire che almeno l'ambiente di sviluppo di Visual Studio è confuso su questo

corretta. Il livello IntelliSense visualizza sempre le informazioni sul metodo per il metodo di sovrascrittura, non il metodo sottoposto a override. La ricerca ha mostrato che gli utenti trovavano confuso quando i metodi venivano fatti apparire come se fossero il metodo di dichiarazione iniziale, non il metodo prevalente. E naturalmente, come ho detto prima, quelli sono i nomi dei parametri che userete per gli argomenti con nome.

+2

Si noti che questo risponde anche perché funziona con solo interfacce (codice di esempio in duplicato in http://stackoverflow.com/questions/27843804/compiling-generic-interface-vs-generic-abstract-class-params-keyword) - non c'è "override", quindi il metodo con 'params' * non è escluso * dalla lista delle possibili corrispondenze. –

5

Penso che sia descritto in 1.6.6.4 comma c specifica #:

un metodo virtuale possono essere ignorate da una classe derivata. Quando una dichiarazione del metodo di istanza include un modificatore di sovrascrittura, il metodo sostituisce un metodo virtuale ereditato con la stessa firma. Mentre una dichiarazione del metodo virtuale introduce un nuovo metodo, la dichiarazione del metodo sostituisce un metodo virtuale ereditato esistente da fornendo una nuova implementazione di tale metodo.

In base a ciò, la dichiarazione del metodo virtual è davvero importante qui. E la dichiarazione del metodo virtual viene utilizzata per ogni chiamata a quel metodo.Correggere le implementazioni override n (se specificato) in runtime, dove params non ha nulla a che fare.

può essere confermato da semplice test:

class Giraffid 
{ 
    public virtual void Eat(params int[] leaves) 
    { 
     Console.WriteLine("G"); 
    } 
} 
class Okapi : Giraffid 
{ 
    public override void Eat(int[] leaves) 
    { 
     Console.WriteLine("O"); 
    } 
} 

Con questa dichiarazione

var o = new Okapi(); 
o.Eat(1, 2, 3); 

funziona bene 100%.

Problemi correlati