web-dev-qa-db-fra.com

Nom du paramètre reflétant: abus des expressions lambda en C # ou brillance de syntaxe?

Je regarde le composant Grid MvcContrib et je suis fasciné, mais en même temps repoussé, par une astuce syntaxique utilisée dans le syntaxe Grid :

.Attributes(style => "width:100%")

La syntaxe ci-dessus définit l'attribut style du code HTML généré sur width:100%. Maintenant, si vous faites attention, 'style' n'est spécifié nulle part, est déduit du nom du paramètre dans l'expression! J'ai dû creuser et trouver où se trouve la "magie":

Hash(params Func<object, TValue>[] hash)
{
    foreach (var func in hash)
    {
        Add(func.Method.GetParameters()[0].Name, func(null));
    }
}

Le code utilise donc le nom formel, le temps de compilation, le nom des paramètres pour créer le dictionnaire des paires attribut nom-valeur. La construction syntaxique résultante est très expressive, mais en même temps très dangereuse. L'utilisation générale des expressions lambda permet de remplacer le noms utilisé sans effet secondaire. Je vois un exemple dans un livre qui dit collection.ForEach(book => Fire.Burn(book)) je sais que je peux écrire dans mon code collection.ForEach(log => Fire.Burn(log)) et cela signifie la même chose. Mais avec la syntaxe MvcContrib Grid ici tout à coup, je trouve un code qui recherche activement et prend des décisions en fonction des noms que j'ai choisis pour mes variables!

Cette pratique est-elle courante dans la communauté C # 3.5/4.0 et les amoureux des expressions lambda? Ou est-ce un non-conformiste malhonnête dont je ne devrais pas m'inquiéter?

423
Remus Rusanu

Cela a mauvaise interop. Par exemple, considérons cet exemple C # - F #

C #:

public class Class1
{
    public static void Foo(Func<object, string> f)
    {
        Console.WriteLine(f.Method.GetParameters()[0].Name);
    }
}

F#:

Class1.Foo(fun yadda -> "hello")

Résultat:

"arg" est imprimé (pas "yadda").

En conséquence, les concepteurs de bibliothèques doivent éviter ce type d’abus ou au moins fournir une surcharge "standard" (par exemple, qui prend le nom de la chaîne comme paramètre supplémentaire) s’ils souhaitent une bonne interopérabilité entre les langages .Net.

146
Brian

Je trouve cela étrange, pas tant à cause du nom, mais parce que lambda est inutile ; il pourrait utiliser un type anonyme et être plus flexible:

.Attributes(new { style = "width:100%", @class="foo", blip=123 });

Ceci est un modèle utilisé dans une grande partie d'ASP.NET MVC (par exemple), et a autres utilisations (a mise en garde , notez également pensées d'Ayende si le nom est une valeur magique plutôt que spécifique à l'appelant)

154
Marc Gravell

Je voulais juste jeter à mon avis (je suis l'auteur du composant de grille MvcContrib).

C’est vraiment un abus de langage - aucun doute là-dessus. Cependant, je ne le considérerais pas vraiment contre-intuitif - quand vous regardez un appel à Attributes(style => "width:100%", @class => "foo")
Je pense que ce qui se passe est assez évident (ce n’est certainement pas pire que l’approche de type anonyme). D'un point de vue intellisense, je conviens que c'est assez opaque.

Pour les personnes intéressées, des informations générales sur son utilisation dans MvcContrib ...

J'ai ajouté ceci à la grille en tant que préférence personnelle - je n'aime pas l'utilisation de types anonymes en tant que dictionnaires (le paramètre qui prend "objet" est aussi opaque que celui qui prend les paramètres Func []) et l'initialiseur de la collection de dictionnaires est plutôt prolixe (je ne suis pas non plus un fan d’interfaces parlées parlées, p. ex. je dois chaîner plusieurs appels à un attribut ("style", "display: none"). Attribut ("classe", "foo"), etc.)

Si C # avait une syntaxe moins détaillée pour les littéraux de dictionnaire, je n'aurais pas pris la peine d'inclure cette syntaxe dans le composant grid :)

Je tiens également à souligner que l'utilisation de cela dans MvcContrib est totalement facultative. Ce sont des méthodes d'extension qui encapsulent les surcharges qui prennent un IDictionary à la place. Je pense qu'il est important que, si vous fournissez une méthode comme celle-ci, vous souteniez également une approche plus "normale", par exemple pour l'interopérabilité avec d'autres langues.

En outre, quelqu'un a mentionné le "temps système de réflexion" et je voulais simplement souligner que cette approche ne présente pas vraiment de frais généraux. Il n'y a pas de compilation de réflexions ou d'expressions à l'exécution (voir http: // blog .bittercoder.com/PermaLink, guid, 206e64d1-29ae-4362-874b-83f5b103727f.aspx ).

137
Jeremy Skinner

Je préférerais

Attributes.Add(string name, string value);

C'est beaucoup plus explicite et standard et l'utilisation de lambdas n'apporte rien.

48
NotDan

Bienvenue sur Rails Land :)

Il n'y a vraiment rien de mal à cela tant que vous savez ce qui se passe. (C'est quand ce genre de chose n'est pas bien documenté qu'il y a un problème).

L'intégralité du framework Rails est construite sur l'idée de convention sur configuration. Nommer les choses d'une certaine manière vous transforme en convention qu'ils utilisent et vous obtenez une multitude de fonctionnalités gratuitement. Suivre la convention de dénomination vous permet d’aller plus vite.

Un autre endroit où j'ai vu un truc comme celui-ci est celui des assertions d'appels de méthodes dans Moq. Vous passez dans un lambda, mais le lambda n'est jamais exécuté. Ils utilisent simplement l'expression pour s'assurer que l'appel de la méthode s'est bien passé et lèvent une exception si ce n'est pas le cas.

46
Jason Punyon

C'est horrible à plus d'un niveau. Et non, ce n'est pas comme Ruby. C'est un abus de C # et .Net.

Beaucoup de suggestions ont été faites sur la façon de procéder de manière plus simple: tuples, types anonymes, interface fluide, etc.

Ce qui le rend si mauvais, c'est que c'est une façon juste de se faire plaisir:

  • Que se passe-t-il lorsque vous devez appeler cela depuis VB?

    .Attributes(Function(style) "width:100%")

  • Intellisense, qui est complètement contre-intuitif, n’apportera aucune aide pour comprendre comment transmettre des informations.

  • C'est inutilement inefficace.

  • Personne ne saura comment le maintenir.

  • Quel est le type d'argument renvoyant aux attributs, est-il Func<object,string>? Comment cette intention est-elle révélatrice? Quelle est votre documentation intellisense va dire, "S'il vous plaît ne pas tenir compte de toutes les valeurs d'objet"

Je pense que vous êtes complètement justifié d'avoir ces sentiments de dégoût.

42
Sam Saffron

Je suis dans le camp "Brillance de syntaxe", s'ils documentent cela clairement, et ça a l'air si cool, ça ne pose presque aucun problème imo!

40
Blindy

Les deux. C'est un abus des expressions lambda ET Brillance de la syntaxe.

37
Arnis Lapsa

Je n'ai pratiquement jamais rencontré ce type d'usage. Je pense que c'est "inapproprié" :)

Ce n'est pas une façon courante d'utiliser, c'est incompatible avec les conventions générales. Ce type de syntaxe a bien sûr des avantages et des inconvénients:

Cons

  • Le code n'est pas intuitif (les conventions habituelles sont différentes)
  • Il a tendance à être fragile (renommer le paramètre annulera la fonctionnalité).
  • C'est un peu plus difficile à tester (simuler l'API nécessitera l'utilisation de la réflexion dans les tests).
  • Si l'expression est utilisée de manière intensive, elle sera plus lente en raison de la nécessité d'analyser le paramètre et pas uniquement la valeur (coût de réflexion).

Pros

  • Il est plus lisible une fois que le développeur s’est adapté à cette syntaxe.

Conclusion - dans la conception des API publiques, j'aurais choisi une méthode plus explicite.

21
Elisha

Non, ce n'est certainement pas une pratique courante. C'est contre-intuitif, il n'y a aucun moyen de regarder le code pour comprendre ce qu'il fait. Vous devez savoir comment il est utilisé pour comprendre comment il est utilisé.

Au lieu de fournir des attributs à l'aide d'un tableau de délégués, les méthodes de chaînage seraient plus claires et plus performantes:

.Attribute("style", "width:100%;").Attribute("class", "test")

Bien que ce soit un peu plus difficile à saisir, c'est clair et intuitif.

18
Guffa

Quel est le problème avec ce qui suit:

html.Attributes["style"] = "width:100%";
17
Tommy Carlier

Tout ce que nous disons à propos de "l’horreur" est une bande de mecs c # de longue date qui réagissent de manière excessive (et je suis un programmeur C # de longue date et je suis toujours un très grand fan du langage). Il n'y a rien d'horrible à propos de cette syntaxe. C'est simplement une tentative de rendre la syntaxe plus semblable à ce que vous essayez d'exprimer. Moins il y a de "bruit" dans la syntaxe de quelque chose, plus le programmeur peut le comprendre facilement. Réduire le bruit dans une seule ligne de code n'aide que peu, mais cela s'accumule dans de plus en plus de code, ce qui s'avère être un avantage substantiel.

C'est une tentative de l'auteur de rechercher les mêmes avantages que les DSL vous offrent: lorsque le code "ressemble" à ce que vous essayez de dire, vous êtes arrivé à un endroit magique. Vous pouvez vous demander si cela est bon pour l'interopérabilité ou s'il est suffisamment meilleur que des méthodes anonymes pour justifier une partie du coût de la "complexité". Assez juste ... alors, dans votre projet, vous devez faire le bon choix d'utiliser ce type de syntaxe. Mais quand même… c’est une tentative intelligente d’un programmeur de faire ce que, finalement, nous essayons tous de faire (que nous nous en rendions compte ou non). Et ce que nous essayons tous de faire, c'est ceci: "Dites à l'ordinateur ce que nous voulons qu'il fasse dans un langage aussi proche que possible de la façon dont nous pensons à ce qu'il veut faire."

Se rapprocher d'exprimer nos instructions aux ordinateurs de la même manière que nous le pensons en interne est essentiel pour rendre les logiciels plus faciles à gérer et plus précis.

EDIT: Je vous avais dit "la clé pour rendre les logiciels plus faciles à maintenir et plus précis", ce qui est un brin de surenchère un peu naïf et exagéré. Je l'ai changé en "une clé".

17
Charlie Flowers

Puis-je l'utiliser pour forger une phrase?

magie lambda (n): fonction lambda utilisée uniquement pour remplacer une chaîne magique.

17
citizenmatt

C'est l'un des avantages des arbres d'expression - on peut examiner le code lui-même pour obtenir des informations supplémentaires. C’est ainsi que .Where(e => e.Name == "Jamie") peut être converti en une clause SQL équivalente Where. C'est une utilisation intelligente des arbres d'expression, bien que j'espère que cela ne va pas plus loin. Tout ce qui est plus complexe risque d’être plus difficile que le code qu’il espère remplacer. J’imagine donc qu’il sera auto-limitant.

12
Jamie Penney

C'est une approche intéressante. Si vous avez limité le côté droit de l’expression à des constantes, vous pouvez implémenter en utilisant

Expression<Func<object, string>>

Je pense que c'est ce que vous voulez vraiment au lieu du délégué (vous utilisez le lambda pour obtenir les noms des deux côtés). Voir l'implémentation naïve ci-dessous:

public static IDictionary<string, string> Hash(params Expression<Func<object, string>>[] hash) {
    Dictionary<string, string> values = new Dictionary<string,string>();
    foreach (var func in hash) {
        values[func.Parameters[0].Name] = ((ConstantExpression)func.Body).Value.ToString();
    }
    return values;
}

Cela pourrait même résoudre le problème d'interopérabilité multilingue mentionné plus tôt dans le fil de discussion.

7
davidfowl

Le code est très intelligent, mais il pose potentiellement plus de problèmes qu’il résout.

Comme vous l'avez souligné, il existe désormais une dépendance obscure entre le nom du paramètre (style) et un attribut HTML. Aucune vérification du temps de compilation n'est effectuée. Si le nom du paramètre est mal saisi, la page n'aura probablement pas de message d'erreur d'exécution, mais un bogue logique beaucoup plus difficile à trouver (pas d'erreur, mais un comportement incorrect).

Une meilleure solution serait d’avoir un membre de données pouvant être vérifié au moment de la compilation. Donc au lieu de cela:

.Attributes(style => "width:100%");

le code avec une propriété Style pourrait être vérifié par le compilateur:

.Attributes.Style = "width:100%";

ou même:

.Attributes.Style.Width.Percent = 100;

Cela représente plus de travail pour les auteurs du code, mais cette approche tire parti des capacités de vérification de type puissantes de C #, qui permettent d’empêcher les bogues de pénétrer dans le code.

6
Chris R. Timmons

en effet, cela ressemble à Ruby =), du moins pour moi, l’utilisation d’une ressource statique pour une "recherche" dynamique ultérieure ne convient pas pour des considérations de conception d’API, espérons que cette astuce intelligente est optionnelle dans cette api.

Nous pourrions hériter d'IDictionary (ou non) et fournir un indexeur qui se comporte comme un tableau php lorsque vous n'avez pas besoin d'ajouter une clé pour définir une valeur. Ce sera une utilisation valide de la sémantique .net, pas seulement de C #, et elle a encore besoin de documentation.

j'espère que cela t'aides

5
Horacio N. Hdez.

IMHO, c'est une façon cool de le faire. Nous aimons tous le fait que nommer un contrôleur de classe en fera un contrôleur dans MVC, n'est-ce pas? Il y a donc des cas où la dénomination importe vraiment.

L'intention est également très claire ici. Il est très facile de comprendre que .Attribute( book => "something") donnera book="something" Et .Attribute( log => "something") donnera log="something"

Je suppose que cela ne devrait pas être un problème si vous le traitez comme une convention. Je suis d'avis que tout ce qui vous fait écrire moins de code et rend l'intention évidente est une bonne chose.

5
madaboutcode

À mon avis, c'est un abus des lambdas.

Quant à la brillance de la syntaxe, je trouve style=>"width:100%" un peu déroutant. Particulièrement à cause du => au lieu de =

4
mfeingold

Si les noms de méthode (func) sont bien choisis, il s'agit d'un excellent moyen d'éviter les problèmes de maintenance (c'est-à-dire: ajoutez une nouvelle fonctionnalité, mais vous avez oublié de l'ajouter à la liste de mappage des paramètres de fonction). Bien sûr, vous devez beaucoup documenter et vous feriez mieux de générer automatiquement la documentation pour les paramètres à partir de la documentation des fonctions de cette classe ...

3
Craig Trader

Je pense que ce n'est pas mieux que les "chaînes magiques". Je ne suis pas très fan des types anonymes non plus pour cela. Cela nécessite une approche meilleure et fortement typée.

1
JamesK