2010-11-04 10 views
12

Questo snippet non è compilato in LINQPad.Perché non delegare la controvarianza funziona con i tipi di valore?

void Main() 
{ 
    (new[]{0,1,2,3}).Where(IsNull).Dump(); 
} 

static bool IsNull(object arg) { return arg == null; } 

messaggio di errore del compilatore è:

Nessun sovraccarico di 'UserQuery.IsNull (oggetto)' partite delegato 'System.Func'

Si lavora per un array di stringhe, ma non funziona per int[]. Apparentemente è legato al pugilato, ma voglio sapere i dettagli.

+0

Deve essere '. Dove (x => IsNull (x))'? –

+0

@Joel Etherton: stessa cosa (quasi). – leppie

+0

Prova a rendere 'IsNull' generico. Rottami, questo è quello che stai chiedendo :) – leppie

risposta

38

La risposta data (che non c'è varianza che coinvolgono i tipi di valore) è corretta. Il motivo per cui covarianza e controvarianza non funzionano quando uno degli argomenti di tipo variabile è un tipo di valore è il seguente. Supponiamo che funzioni e mostri come vanno le cose in modo orribile:

Func<int> f1 =()=>123; 
Func<object> f2 = f1; // Suppose this were legal. 
object ob = f2(); 

OK, che succede? f2 è di riferimento identico a f1. Quindi qualunque cosa faccia, fa f2. Cosa fa f1? Mette un intero 32 bit in pila. Cosa fa il compito? Prende tutto ciò che è in pila e lo memorizza nella variabile "ob".

Dove sono state le istruzioni di inscatolamento? Non ce n'era uno! Abbiamo appena archiviato un intero a 32 bit nella memoria che non si aspettava un numero intero ma piuttosto un puntatore a 64 bit su una posizione heap contenente un numero intero in scatola. Quindi avete semplicemente disallineato lo stack e corrotto il contenuto della variabile con un riferimento non valido. Presto il processo andrà a fuoco.

Quindi dove dovrebbero andare le istruzioni di boxe? Il compilatore deve generare un'istruzione di boxe da qualche parte. Non può andare dopo la chiamata a f2, perché il compilatore crede che f2 restituisca un oggetto che è già stato incassato. Non può andare nella chiamata a f1 perché f1 restituisce un int, non un int inscatolato. Non può passare tra la chiamata a f2 e la chiamata a f1 perché sono lo stesso delegato; non c'è "tra".

L'unica cosa che potevamo fare è rendere la seconda linea in realtà significa:

Func<object> f2 =()=>(object)f1(); 

e ora non abbiamo un'identità riferimento fra f1 e f2 più, quindi qual è il punto della varianza ? L'intero punto di avere conversioni di riferimento covariant è conservare l'identità di riferimento.

Non importa come lo si taglia, le cose vanno terribilmente storte e non c'è modo di risolverlo.Pertanto, la cosa migliore da fare è rendere la funzione illegale in primo luogo; non è consentita alcuna variazione sui tipi di delegati generici in cui un tipo di valore sarebbe la cosa che varia.

AGGIORNAMENTO: Avrei dovuto notare qui nella mia risposta che in VB, è possibile convertire un delegato int-ritorno in un delegato di ritorno dell'oggetto. VB produce semplicemente un secondo delegato che avvolge la chiamata al primo delegato e inserisce il risultato. VB sceglie di abbandonare la restrizione che una conversione di riferimento preserva l'identità dell'oggetto.

Questo illustra una differenza interessante nelle filosofie di progettazione di C# e VB. In C#, il team di progettazione pensa sempre "come può il compilatore trovare quello che potrebbe essere un bug nel programma dell'utente e portarlo alla loro attenzione?" e il team VB sta pensando "come possiamo capire che cosa l'utente volesse realmente accadere e farlo semplicemente per loro conto?" In breve, la filosofia C# è "se vedi qualcosa, dì qualcosa", e la filosofia VB è "fai quello che voglio dire, non quello che dico". Entrambe sono filosofie perfettamente ragionevoli; è interessante vedere come due lingue che hanno set di funzionalità quasi identici differiscano in questi piccoli dettagli a causa dei principi di progettazione.

0

Non funziona per un int perché non ci sono oggetti.

Prova:

void Fun() 
{ 
    IEnumerable<object> objects = (new object[] { 0, 1, null, 3 }).Where(IsNull); 

    foreach (object item in objects) 
    { 
     Console.WriteLine("item is null"); 
    } 
} 

bool IsNull(object arg) { return arg == null; } 
3

Perché Int32 è tipo di valore e contra-varianza non funziona su tipi di valore.

Si può provare questo:

(new **object**[]{0,1,2,3}).Where(IsNull).Dump(); 
+2

Dump() potrebbe essere un metodo di estensione – Konstantin

Problemi correlati