web-dev-qa-db-fra.com

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

J'ai le code suivant:

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

Cependant, ce qui suit ne se 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'affecter une méthode anonyme à une variable locale implicitement typée.

J'ai une supposition et c'est si la version var est compilée, cela 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 aucun sens puisque Func <> n'autorise que jusqu'à 4 arguments (en .NET 3.5, ce que j'utilise). Peut-être que quelqu'un pourrait clarifier le problème. Merci.

132
Marlon

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

Mais on pourrait dire que Func est spécial, et que le type inféré d'une méthode lambda ou anonyme est Func de quelque chose. Nous aurions toujours toutes sortes de problèmes. Quels types souhaiteriez-vous déduire pour les cas suivants?

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

Il n'y a pas Func<T> type qui prend un ref n'importe quoi.

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? Long? Court? Octet?)

var x3 = (int y)=>null;

Nous ne connaissons pas le type de retour, mais il ne peut pas être nul. Le type de retour peut être tout type de référence ou tout 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 destiné à être une instruction lambda à retour 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 prenez en charge aucune de ces fonctionnalités. Soutenez simplement les cas "normaux" où les types peuvent être déterminés. Ça 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 chacun. Nous devons encore spécifier tout ce comportement, le documenter, écrire des tests pour cela, etc. Il s'agit d'une fonctionnalité très coûteuse qui permet à l'utilisateur d'économiser une demi-douzaine de touches. Nous avons de meilleurs moyens d'ajouter de la valeur à la langue que de passer beaucoup de temps à écrire des cas de test pour une fonctionnalité qui ne fonctionne pas la moitié du temps et n'apporte pratiquement aucun avantage dans les cas où cela fonctionne.

La situation où cela 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 ce qu'est le type func.

149
Eric Lippert

Seul Eric Lippert en est sûr, mais je pense que c'est parce que la signature du type délégué ne détermine pas uniquement le type.

Considérez votre exemple:

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

Voici deux inférences possibles pour ce que le var devrait être:

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

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

29
itowlson

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éthodes anonymes sont des expressions sans type en C # 2.0, et les expressions lambda les joignent en C # 3.0. Il est donc illégal pour eux d'apparaître "nus" à droite d'une déclaration implicite.

6
Brian Rasmussen

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

Donc, étant donné un délégué anonyme (ou lambda) comme () => {}, s'agit-il d'un Action ou d'un MethodInvoker? Le compilateur ne peut pas le dire.

De même, si je déclare un type délégué prenant un argument string et retournant 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é.

5
Stephen Cleary

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 demande 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 "variante" et n'indique pas que la variable est typée de manière lâche ou liée tardivement. Cela signifie simplement que le compilateur détermine et attribue le type le plus approprié.

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

Compte tenu des éléments suivants 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 différentes signatures de méthode, le compilateur n'est pas en mesure de déduire correctement quel serait le type le plus approprié à affecter.

2
nybbler

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 du texte comme Func<string, string, int, CustomInputType, bool, ReturnType>? " [1]

Étant le programmeur paresseux/hacky que je suis, j'ai expérimenté avec 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 retourner 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 assez novice chez les délégués ... je voulais juste partager ce que j'ai appris.


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

1
Ambrose Leung

Comment ç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);
0
mmm