Pourquoi une méthode anonyme ne peut-elle pas être attribuée à var?

139

J'ai le code suivant:

Func<string, bool> comparer = delegate(string value) {
    return value != "0";
};

Cependant, ce qui suit ne compile pas:

var comparer = delegate(string value) {
    return value != "0";
};

Pourquoi le compilateur ne peut-il pas comprendre qu'il s'agit d'un Func<string, bool>? Il prend un paramètre de chaîne et renvoie un booléen. Au lieu de cela, cela me donne l'erreur:

Impossible d'attribuer une méthode anonyme à une variable locale typée implicitement.

J'ai une supposition et c'est que si la version var compilait , elle manquerait de cohérence si j'avais ce qui suit:

var comparer = delegate(string arg1, string arg2, string arg3, string arg4, string arg5) {
    return false;
};

Ce qui précède n'aurait pas de sens puisque Func <> n'autorise que jusqu'à 4 arguments (dans .NET 3.5, ce que j'utilise). Peut-être que quelqu'un pourrait clarifier le problème. Merci.

Marlon
la source
3
Remarque concernant votre argument à 4 arguments , dans .NET 4, Func<>accepte jusqu'à 16 arguments.
Anthony Pegram
Merci pour la clarification. J'utilise .NET 3.5.
Marlon
9
Pourquoi faire penser au compilateur que c'est un Func<string, bool>? Cela Converter<string, bool>me ressemble !
Ben Voigt
3
parfois je manque VB ..Dim comparer = Function(value$) value <> "0"
Slai

Réponses:

155

D'autres ont déjà souligné qu'il existe une infinité de types de délégués possibles que vous auriez pu signifier; ce qui est si spécial Funcqu'il mérite d'être la valeur par défaut au lieu de PredicateouAction ou toute autre possibilité? Et, pour les lambdas, pourquoi est-il évident que l'intention est de choisir la forme de délégué, plutôt que la forme d'arbre d'expression?

Mais nous pourrions dire que Funcc'est spécial, et que le type inféré d'une méthode lambda ou anonyme est Func de quelque chose. Nous aurions encore toutes sortes de problèmes. Quels types aimeriez-vous être déduits pour les cas suivants?

var x1 = (ref int y)=>123;

Il n'y a aucun Func<T>type qui prend un ref quoi que ce soit.

var x2 = y=>123;

Nous ne connaissons pas le type du paramètre formel, bien que nous connaissions le retour. (Ou le faisons-nous? Le retour est-il int? Long? Court? Octet?)

var x3 = (int y)=>null;

Nous ne connaissons pas le type de retour, mais il ne peut pas être annulé. Le type de retour peut être n'importe quel type de référence ou n'importe quel type de valeur Nullable.

var x4 = (int y)=>{ throw new Exception(); }

Encore une fois, nous ne connaissons pas le type de retour, et cette fois, il peut être nul.

var x5 = (int y)=> q += y;

Est-ce que cela est destiné à être une instruction lambda de retour de vide ou quelque chose qui renvoie la valeur qui a été affectée à q? Les deux sont légaux; que devons-nous choisir?

Maintenant, vous pourriez dire, eh bien, ne supportez aucune de ces fonctionnalités. Soutenez simplement les cas "normaux" où les types peuvent être élaborés. Cela n'aide pas. Comment cela me facilite-t-il la vie? Si la fonctionnalité fonctionne parfois et échoue parfois, je dois encore écrire le code pour détecter toutes ces situations d'échec et donner un message d'erreur significatif pour chacune. Nous devons encore spécifier tout ce comportement, le documenter, écrire des tests pour celui-ci, etc. C'est une fonctionnalité très coûteuse qui permet à l'utilisateur d'économiser peut-être une demi-douzaine de frappes. Nous avons de meilleures façons d'ajouter de la valeur au langage que de passer beaucoup de temps à écrire des cas de test pour une fonctionnalité qui ne fonctionne pas la moitié du temps et qui n'apporte pratiquement aucun avantage dans les cas où cela fonctionne.

La situation où il est réellement utile est:

var xAnon = (int y)=>new { Y = y };

parce qu'il n'y a pas de type "parlable" pour cette chose. Mais nous avons ce problème tout le temps, et nous utilisons simplement l'inférence de type de méthode pour déduire le type:

Func<A, R> WorkItOut<A, R>(Func<A, R> f) { return f; }
...
var xAnon = WorkItOut((int y)=>new { Y = y });

et maintenant l'inférence de type de méthode détermine quel est le type func.

Eric Lippert
la source
43
Quand allez-vous compiler vos réponses SO dans un livre? Je l'achèterais :)
Matt Greer
13
J'appuie la proposition d'un livre Eric Lippert de réponses SO. Titre suggéré: "Reflections From The Stack"
Adam Rackis
24
@Eric: Bonne réponse, mais il est légèrement trompeur d'illustrer cela comme quelque chose qui n'est pas possible, car cela fonctionne vraiment très bien dans D.C'est juste que vous n'avez pas choisi de donner aux délégués leur propre type, et les ont plutôt dépendants sur leurs contextes ... donc à mon humble avis, la réponse devrait être "parce que c'est ainsi que nous l'avons fait" plus que toute autre chose. :)
user541686
5
@abstractdissonance Je note également que le compilateur est open source. Si vous vous souciez de cette fonctionnalité, vous pouvez donner le temps et les efforts nécessaires pour y parvenir. Je vous encourage à soumettre une pull request.
Eric Lippert
7
@AbstractDissonance: Nous avons mesuré le coût en termes de ressources limitées: développeurs et temps. Cette responsabilité n'a pas été accordée par Dieu; il a été imposé par le vice-président de la division développeur. L'idée que l'équipe C # pourrait d'une manière ou d'une autre ignorer un processus budgétaire est étrange. Je vous assure que les compromis ont été et sont toujours faits par la considération attentive et réfléchie d'experts qui ont fait exprimer leurs souhaits aux communautés C #, la mission stratégique de Microsoft et leur propre goût du design à l'esprit.
Eric Lippert
29

Seul Eric Lippert le sait, mais je pense que c'est parce que la signature du type de délégué ne détermine pas de manière unique le type.

Considérez votre exemple:

var comparer = delegate(string value) { return value != "0"; };

Voici deux inférences possibles pour ce que vardevrait être:

Predicate<string> comparer  = delegate(string value) { return value != "0"; };  // okay
Func<string, bool> comparer = delegate(string value) { return value != "0"; };  // also okay

Lequel doit déduire le compilateur? Il n'y a aucune bonne raison de choisir l'un ou l'autre. Et bien que a Predicate<T>soit fonctionnellement équivalent à a Func<T, bool>, ils sont toujours de types différents au niveau du système de type .NET. Le compilateur ne peut donc pas résoudre sans ambiguïté le type de délégué et doit échouer l'inférence de type.

Itowlson
la source
1
Je suis sûr que de nombreuses autres personnes chez Microsoft le savent également. ;) Mais oui, vous faites allusion à une raison principale, le type de temps de compilation ne peut pas être déterminé car il n'y en a pas. La section 8.5.1 de la spécification du langage met spécifiquement en évidence cette raison pour interdire l'utilisation de fonctions anonymes dans des déclarations de variables typées implicitement.
Anthony Pegram
3
Oui. Et pire encore, pour les lambdas, nous ne savons même pas si cela va à un type délégué; ce pourrait être un arbre d'expression.
Eric Lippert
Pour toute personne intéressée, je l' ai écrit un peu plus à ce sujet et comment le C # et F # approche contraste à mindscapehq.com/blog/index.php/2011/02/23/...
itowlson
pourquoi le compilateur ne peut-il pas simplement fabriquer un nouveau type unique comme le fait C ++ pour sa fonction lambda
Weipeng L
En quoi diffèrent-ils «au niveau du système de type .NET»?
arao6
6

Eric Lippert a un vieux post à ce sujet où il dit

Et en fait, la spécification C # 2.0 appelle cela. Les expressions de groupe de méthodes et les expressions de méthode anonymes sont des expressions sans type dans C # 2.0, et les expressions lambda les rejoignent dans C # 3.0. Par conséquent, il est illégal pour eux d'apparaître "nus" sur le côté droit d'une déclaration implicite.

Brian Rasmussen
la source
Et cela est souligné par la section 8.5.1 de la spécification du langage. «L'expression d'initialisation doit avoir un type à la compilation» pour être utilisée pour une variable locale typée implicitement.
Anthony Pegram
5

Différents délégués sont considérés comme des types différents. par exemple, Actionest différent de MethodInvoker, et une instance de Actionne peut pas être affectée à une variable de type MethodInvoker.

Donc, étant donné un délégué anonyme (ou lambda) comme () => {}, est-ce un Actionou un MethodInvoker? Le compilateur ne peut pas le dire.

De même, si je déclare un type de délégué prenant un stringargument et renvoyant un bool, comment le compilateur saurait-il que vous vouliez vraiment un Func<string, bool>au lieu de mon type de délégué? Il ne peut pas déduire le type de délégué.

Stephen Cleary
la source
2

Les points suivants proviennent du MSDN concernant les variables locales implicitement typées:

  1. var ne peut être utilisé que lorsqu'une variable locale est déclarée et initialisée dans la même instruction; la variable ne peut pas être initialisée à null, ni à un groupe de méthodes ou à une fonction anonyme.
  2. Le mot-clé var indique au compilateur de déduire le type de la variable à partir de l'expression sur le côté droit de l'instruction d'initialisation.
  3. Il est important de comprendre que le mot-clé var ne signifie pas "variant" et n'indique pas que la variable est mal typée ou à liaison tardive. Cela signifie simplement que le compilateur détermine et attribue le type le plus approprié.

Référence MSDN: variables locales typées implicitement

Considérant ce qui suit concernant les méthodes anonymes:

  1. Les méthodes anonymes vous permettent d'omettre la liste des paramètres.

Référence MSDN: méthodes anonymes

Je soupçonne que puisque la méthode anonyme peut en fait avoir des signatures de méthode différentes, le compilateur est incapable de déduire correctement quel serait le type le plus approprié à attribuer.

Nybbler
la source
1

Mon message ne répond pas à la question réelle, mais il répond à la question sous-jacente de:

"Comment puis-je éviter d'avoir à taper des caractères fugaces comme Func<string, string, int, CustomInputType, bool, ReturnType>?" [1]

Étant le programmeur paresseux / hacky que je suis, j'ai expérimenté l'utilisation Func<dynamic, object>- qui prend un seul paramètre d'entrée et renvoie un objet.

Pour plusieurs arguments, vous pouvez l'utiliser comme ceci:

dynamic myParams = new ExpandoObject();
myParams.arg0 = "whatever";
myParams.arg1 = 3;
Func<dynamic, object> y = (dynObj) =>
{
    return dynObj.arg0.ToUpper() + (dynObj.arg1 * 45); //screw type casting, amirite?
};
Console.WriteLine(y(myParams));

Conseil: vous pouvez utiliser Action<dynamic>si vous n'avez pas besoin de renvoyer un objet.

Oui, je sais que cela va probablement à l'encontre de vos principes de programmation, mais cela a du sens pour moi et probablement pour certains codeurs Python.

Je suis plutôt novice chez les délégués ... je voulais juste partager ce que j'ai appris.


[1] Cela suppose que vous Funcn'appelez pas une méthode qui nécessite un paramètre prédéfini , auquel cas vous devrez taper cette fugly string: /

Ambrose Leung
la source
0

Et ça?

var item = new
    {
        toolisn = 100,
        LangId = "ENG",
        toolPath = (Func<int, string, string>) delegate(int toolisn, string LangId)
        {
              var path = "/Content/Tool_" + toolisn + "_" + LangId + "/story.html";
              return File.Exists(Server.MapPath(path)) ? "<a style=\"vertical-align:super\" href=\"" + path + "\" target=\"_blank\">execute example</a> " : "";
        }
};

string result = item.toolPath(item.toolisn, item.LangId);
mmm
la source