433 votes

Reflecting parameter name : abus des expressions lambda de C# ou brillance de la syntaxe ?

Je regarde le MvcContrib Je suis fasciné, et en même temps dégoûté, par une astuce syntaxique utilisée dans le composant de la grille. Syntaxe de la grille :

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

La syntaxe ci-dessus donne à l'attribut style du HTML généré la valeur suivante width:100% . Si vous faites attention, le "style" n'est spécifié nulle part. Il est déduit du nom du paramètre dans l'expression ! J'ai dû creuser cette question et j'ai trouvé où la "magie" se produit :

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

Donc, en effet, le code utilise le nom formel, au moment de la compilation, des paramètres pour créer le dictionnaire des paires nom-valeur des attributs. La construction syntaxique qui en résulte est en effet très expressive, mais en même temps très dangereuse.

L'utilisation générale des expressions lambda permet de remplacer l'expression 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 de la grille MvcContrib, tout d'un coup, je trouve du code qui regarde activement et prend des décisions basées sur les noms que je choisis pour mes variables !

S'agit-il d'une pratique courante au sein de la communauté C# 3.5/4.0 et des amateurs d'expressions lambda ? Ou s'agit-il d'un cas isolé dont je ne devrais pas m'inquiéter ?

10 votes

+1 pour avoir apporté le hashrocket à C#

2 votes

Changer foreach (var func in hash) en : Array.ForEarch(hash, func => Add(func.Method.GetParameters()[0].Name, func(null))) ;

34 votes

Je dirais que cela semble évident, tant que l'on est prêt à regarder l'intention du code, plutôt que de simplement analyser la syntaxe. Ce qui, si vous lisez du bon code, est ce que vous devriez faire de toute façon. La syntaxe n'est qu'un véhicule pour l'intention, et je dirais que c'est l'intention qui révèle le code.

153voto

Marc Gravell Points 482669

Je trouve ça étrange, pas tant à cause de la nom mais parce que le lambda est inutile il pourrait utiliser un type d'anonymat et être plus souple :

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

Il s'agit d'un modèle utilisé dans une grande partie d'ASP.NET MVC (par exemple), et a autres usages (a avertissement , notez également Les pensées d'Ayende si le nom est une valeur magique plutôt que spécifique à l'appelant)

4 votes

Cela pose également des problèmes d'interopérabilité ; tous les langages ne prennent pas en charge la création d'un type anonyme de ce type à la volée.

5 votes

J'adore le fait que personne n'ait vraiment répondu à la question, au lieu de fournir un argument "ceci est mieux". :p Est-ce un abus ou non ?

1 votes

C'est une question de lisibilité. Cette approche implique davantage d'accolades et de new mot-clé. Pourriez-vous ajouter quelques réflexions sur les raisons pour lesquelles cela (utilisation du type anonyme) est more flexible ?

149voto

Brian Points 82719

L'interopérabilité est faible. Par exemple, considérez 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é (et non "yadda").

Par conséquent, les concepteurs de bibliothèques devraient soit éviter ce genre d'"abus", soit au moins fournir une surcharge "standard" (par exemple, qui prend le nom de la chaîne comme paramètre supplémentaire) s'ils veulent avoir une bonne interopérabilité entre les langages .Net.

0 votes

Une chose que je n'avais pas envisagée. Comment accéder correctement à l'arbre d'expression pour que cela fonctionne dans le cas de F# ?

11 votes

Tu ne le fais pas. Cette stratégie est tout simplement non-portable. (Au cas où cela vous aiderait, comme exemple dans l'autre sens, F# pourrait être capable de surcharger des méthodes qui ne diffèrent que par leur type de retour (l'inférence de type peut le faire). Cela peut être exprimé dans le CLR. Mais F# l'interdit, principalement parce que si vous le faisiez, ces API ne seraient pas appelables depuis C#). Lorsqu'il s'agit d'interopérabilité, il y a toujours des compromis à faire au niveau des fonctionnalités "périphériques" concernant les avantages que vous obtenez par rapport à l'interopérabilité que vous abandonnez.

0 votes

Cela semble être un oubli de la part du langage F# -> y a-t-il une bonne raison pour laquelle c'est le cas ? Je suppose que les expressions lambda en C# et les fonctions en F# génèrent des IL différentes.

137voto

Jeremy Skinner Points 721

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

Il s'agit d'un abus de langage, sans aucun doute. Cependant, je ne considérerais pas vraiment cela comme étant contre intuitif - lorsque 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 suis d'accord pour dire que c'est assez opaque.

Pour ceux qui sont intéressés, quelques informations de base sur son utilisation dans MvcContrib...

J'ai ajouté ceci à la grille par préférence personnelle - je n'aime pas l'utilisation de types anonymes comme dictionnaires (avoir un paramètre qui prend "object" est tout aussi opaque que celui qui prend des paramètres Func[]) et l'initialisateur de collection Dictionary est plutôt verbeux (je ne suis pas non plus un fan des interfaces fluides verbeuses, par exemple devoir enchaîner plusieurs appels à un Attribute("style", "display:none").Attribute("class", "foo") etc)

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

Je veux aussi souligner que l'utilisation de ceci dans MvcContrib est complètement optionnelle - ce sont des méthodes d'extension qui enveloppent 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 devriez également prendre en charge une approche plus "normale", par exemple pour l'interopérabilité avec d'autres langages.

De plus, quelqu'un a mentionné la "surcharge de réflexion" et je voulais juste souligner qu'il n'y a pas vraiment de surcharge avec cette approche - il n'y a pas de réflexion à l'exécution ou de compilation d'expression impliquée (voir http://blog.bittercoder.com/PermaLink,guid,206e64d1-29ae-4362-874b-83f5b103727f.aspx ).

2 votes

J'ai également essayé d'aborder certaines des questions soulevées ici de manière plus approfondie sur mon blog : jeremyskinner.co.uk/2009/12/02/lambda-abuse-the-mvccontrib-hash

4 votes

Ce n'est pas moins opaque dans Intellisense que les objets anonymes.

23 votes

+1 pour avoir mentionné que l'interface est ajoutée via en option méthodes d'extension. Les non-utilisateurs de C# (et toute personne offensée par l'abus de langage) peuvent simplement s'abstenir de l'utiliser.

49voto

NotDan Points 9519

Je préférerais

Attributes.Add(string name, string value);

C'est beaucoup plus explicite et standard et on ne gagne rien à utiliser des lambdas.

20 votes

Mais est-ce le cas ? html.Attributes.Add("style", "width:100%"); ne se lit pas aussi bien que style = "width:100%" (le html réellement généré), alors que style => "width:100%" est très proche de ce à quoi il ressemble dans le HTML résultant.

6 votes

Leur syntaxe permet des astuces comme .Attributes(id=>'foo', @class=>'bar', style=>'width:100%'). La signature de la fonction utilise la syntaxe params pour un nombre variable d'args : Attributes(params Func<objet, objet>[] args). C'est très puissant, mais il m'a fallu un certain temps pour comprendre ce qu'il fait.

27 votes

@Jamie : Essayer de faire en sorte que le code C# ressemble au code HTML serait une mauvaise raison pour les décisions de conception. Ce sont des langages complètement différents pour des objectifs complètement différents, et ils ne devraient pas se ressembler.

45voto

Jason Punyon Points 21244

Bienvenue à Rails Land :)

Il n'y a vraiment rien de mal à cela, tant que vous savez ce qui se passe. (C'est lorsque ce genre de choses 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 la convention par rapport à la configuration. Nommer les choses d'une certaine manière vous permet d'adhérer à une convention qu'ils utilisent et vous obtenez un grand nombre de fonctionnalités gratuitement. Suivre la convention de nommage vous permet d'atteindre votre objectif plus rapidement. L'ensemble fonctionne brillamment.

Un autre endroit où j'ai vu une astuce comme celle-ci est dans les assertions d'appel de méthode dans Moq. Vous passez un lambda, mais celui-ci n'est jamais exécuté. Ils utilisent simplement l'expression pour s'assurer que l'appel de méthode s'est produit et lancent une exception dans le cas contraire.

3 votes

J'ai un peu hésité à le faire, mais je suis d'accord. En dehors de la surcharge de réflexion, il n'y a pas de différence significative entre l'utilisation d'une chaîne de caractères comme dans Add() et l'utilisation d'un nom de paramètre lambda. Du moins, c'est ce que je pense. Vous pouvez vous tromper et taper "sytle" sans vous en rendre compte dans les deux cas.

5 votes

Je n'arrivais pas à comprendre pourquoi ce n'était pas bizarre pour moi, et puis je me suis souvenu de Rails. :D

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X