2010-11-04 26 views
12

Cet extrait n'est pas compilé dans LINQPad.Pourquoi ne pas déléguer le travail de contravariance avec des types de valeur?

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

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

Le message d'erreur du compilateur est:

Aucune surcharge pour 'UserQuery.IsNull (objet)' délégué rencontres 'System.Func'

Il travaille pour un tableau de chaînes, mais ne fonctionne pas pour int[]. C'est apparemment lié à la boxe, mais je veux connaître les détails.

+0

Cela devrait-il être '.Where (x => IsNull (x))'? –

+0

@Joel Etherton: Même chose (presque). – leppie

+0

Essayez de créer un générique 'IsNull'. Scrap that, c'est ce que vous demandez :) – leppie

Répondre

38

La réponse (qu'il n'y a pas d'écart impliquant les types de valeur) est correcte. La covariance et la contravariance de la raison ne fonctionnent pas lorsque l'un des arguments de type variable est un type de valeur est comme suit. Supposons que cela fonctionne et montre que les choses tournent mal:

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

OK, que se passe-t-il? f2 est identique à référence à f1. Donc tout ce que f1 fait, f2 le fait. Que fait f1? Il place un entier de 32 bits sur la pile. Que fait le devoir? Il prend tout ce qui est sur la pile et le stocke dans la variable "ob".

Où était l'instruction de boxe? Il n'y en avait pas! Nous avons juste stocké un entier de 32 bits dans le stockage qui attendait pas un nombre entier mais plutôt un pointeur de 64 bits vers un emplacement de tas contenant un nombre entier encadré. Vous avez donc à la fois désaligné la pile et corrompu le contenu de la variable avec une référence invalide. Bientôt, le processus va tomber dans les flammes.

Alors, où devrait aller l'instruction de boxe? Le compilateur doit générer une instruction de boxe quelque part. Il ne peut pas aller après l'appel à f2, parce que le compilateur croit que f2 renvoie un objet qui a déjà été encadré. Il ne peut pas aller dans l'appel à f1 parce que f1 renvoie un int, pas un int boxed. Il ne peut pas aller entre l'appel à f2 et l'appel à f1 parce qu'ils sont le même délégué; il n'y a pas de 'entre'.

La seule chose que nous pourrions faire ici est faire la deuxième ligne signifie réellement:

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

et maintenant nous n'avons pas l'identité de référence plus entre f1 et f2, donc quel est le point de la variance ? Le point entier d'avoir covariant conversions de référence est à préserver l'identité de référence.

Peu importe comment vous le découpez, les choses tournent mal et il n'y a aucun moyen de le réparer.Par conséquent, la meilleure chose à faire est de rendre la fonctionnalité illégale en premier lieu; il n'y a aucune variance autorisée sur les types de délégués génériques où un type de valeur serait la chose qui varie. MISE À JOUR: J'aurais dû noter ici dans ma réponse que dans 0B, peut convertir un délégué int-retour à un délégué retournant un objet. VB produit simplement un second délégué qui enveloppe l'appel au premier délégué et encadre le résultat. VB choisit d'abandonner la restriction selon laquelle une conversion de référence préserve l'identité de l'objet.

Ceci illustre une différence intéressante dans les philosophies de conception de C# et VB. En C#, l'équipe de conception pense toujours "comment le compilateur peut-il trouver ce qui est susceptible d'être un bogue dans le programme de l'utilisateur et le porter à leur attention?" et l'équipe VB pense "comment pouvons-nous comprendre ce que l'utilisateur a probablement prévu de faire et le faire en son nom?" En bref, la philosophie C# est "si vous voyez quelque chose, dites quelque chose", et la philosophie VB est "fais ce que je veux dire, pas ce que je dis". Les deux sont des philosophies parfaitement raisonnables; Il est intéressant de voir comment deux langues qui ont des ensembles de fonctions presque identiques diffèrent dans ces petits détails en raison des principes de conception.

0

Cela ne fonctionne pas pour un int car il n'y a pas d'objets.

Essayez:

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

Parce que Int32 est le type de valeur et contre-variance ne fonctionne pas sur les types de valeur.

Vous pouvez essayer celui-ci:

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

Dump() pourrait être une méthode d'extension – Konstantin