Nombre del parámetro reflector: ¿abuso de expresiones lambda de C # o brillo de sintaxis?


428

Estoy mirando el componente MvcContrib Grid y estoy fascinado, pero al mismo tiempo rechazado, por un truco sintáctico utilizado en la sintaxis de Grid :

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

La sintaxis anterior establece el atributo de estilo del HTML generado en width:100%. Ahora, si prestas atención, 'estilo' no se especifica en ninguna parte. Se deduce del nombre del parámetro en la expresión! Tuve que profundizar en esto y descubrí dónde sucede la 'magia':

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

De hecho, el código está usando el tiempo formal de compilación, el nombre de los parámetros para crear el diccionario de pares de nombre-valor de atributo. La construcción de sintaxis resultante es muy expresiva, pero al mismo tiempo muy peligrosa.

El uso general de las expresiones lambda permite el reemplazo de los nombres utilizados sin efectos secundarios. Veo un ejemplo en un libro que dice collection.ForEach(book => Fire.Burn(book))que sé que puedo escribir en mi código collection.ForEach(log => Fire.Burn(log))y significa lo mismo . ¡Pero con la sintaxis de MvcContrib Grid aquí de repente, encuentro un código que busca activamente y toma decisiones basadas en los nombres que elijo para mis variables!

Entonces, ¿es esta práctica común con la comunidad C # 3.5 / 4.0 y los amantes de las expresiones lambda? ¿O es un pícaro truco inconformista del que no debería preocuparme?


34
Yo diría que esto parece obvio, siempre que esté dispuesto a analizar la intención del código, en lugar de simplemente analizar la sintaxis. Lo cual, si está leyendo un buen código, es lo que debería estar haciendo de todos modos. La sintaxis es solo un vehículo para la intención, y diría que este es un código que revela la intención.
Jamie Penney el

191
Le pregunté a Anders (y al resto del equipo de diseño) qué pensaban. Digamos que los resultados no se podrían imprimir en un periódico familiar.
Eric Lippert el

22
Actualmente, a C # le falta una sintaxis clara y clara para los mapas, especialmente los mapas pasados ​​a las funciones. Varios lenguajes dinámicos (Ruby, Python, JavaScript, ColdFusion 9) tienen una sintaxis clara y clara para eso, en cierta medida u otra.
yfeldblum

28
Oh, creo que se ve encantador. En cuanto a una sintaxis para mapas, sí, sería genial si pudiéramos hacer nuevos {{"Do", "A deer"}, {"Re", "golden sun"} ...} y hacer que el compilador deduzca el construcción de un mapa, del mismo modo que new [] {1, 2,3} infiere la construcción de una matriz int. Estamos considerando este tipo de cosas.
Eric Lippert el

17
Eric Lippert, te respeto mucho, pero creo que tú y el grupo (incluido Anders, a quien respeto FREAKIN 'TREMENDOUSLY) lo están criticando demasiado. Como admite, C # carece de una sintaxis ajustada para los mapas, y algunos otros idiomas (como Ruby) tienen excelentes. Este tipo encontró la manera de obtener la sintaxis que quería. Te concederé que hay formas similares de expresarlo que son casi tan expresivas como su sintaxis con menos inconvenientes. Pero su sintaxis y el hecho de que trabajó tan duro para obtenerla muestran claramente la necesidad de mejorar el lenguaje. El rendimiento pasado indica que ustedes crearán algo grandioso para ello.
Charlie Flowers

Respuestas:


147

Esto tiene mala interoperabilidad. Por ejemplo, considere este ejemplo de 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")

Resultado:

Se imprime "arg" (no "yadda").

Como resultado, los diseñadores de bibliotecas deben evitar este tipo de 'abusos' o, al menos, proporcionar una sobrecarga 'estándar' (por ejemplo, que toma el nombre de la cadena como un parámetro adicional) si quieren tener una buena interoperabilidad entre los idiomas .Net.


11
Usted no Esta estrategia es simplemente no portátil. (En caso de que ayude, como un ejemplo a la inversa, F # podría ser capaz de sobrecargar métodos que difieren solo en el tipo de retorno (la inferencia de tipo puede hacer eso). Esto se puede expresar en el CLR. Pero F # no lo permite, principalmente porque si usted hizo eso, esas API no serían invocables desde C #.) Cuando se trata de interoperabilidad, siempre hay compensaciones en las características 'de borde' con respecto a los beneficios que obtiene frente a la interoperabilidad que intercambia.
Brian

32
No me gusta la no interoperabilidad como razón para no hacer algo. Si la interoperabilidad es un requisito, hágalo, si no, ¿por qué preocuparse? Esta es YAGNI en mi humilde opinión.
John Farrell el

15
@jfar: en .NET CLR la interoperabilidad de la tierra tiene una dimensión completamente nueva, ya que se supone que los ensamblados generados en cualquier compilador se consumen desde cualquier otro lenguaje.
Remus Rusanu

25
Estoy de acuerdo en que no tiene que ser compatible con CLS, pero parece una buena idea si está escribiendo una biblioteca o control (y el fragmento que inicia esto es de una cuadrícula, ¿sí?) De lo contrario, solo está limitando su audiencia / base de clientes
JMarsch

44
Quizás valga la pena cambiar: Func <objeto, cadena> a Expresión << Func <objeto, cadena >> Y si restringe el lado derecho de la expresión para que solo sean constantes, puede tener una implementación que haga esto: public static IDictionary <string, string> Hash (params Expression <Func <object, string >> [] hash) {Dictionary <string, string> values ​​= new Dictionary <string, string> (); foreach (var func en hash) {valores [func.Parameters [0] .Name] = (string) ((ConstantExpression) func.Body) .Value; } valores de retorno; }
davidfowl 03 de

154

Me parece extraño no tanto por el nombre , sino porque la lambda es innecesaria ; podría usar un tipo anónimo y ser más flexible:

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

Este es un patrón utilizado en gran parte de ASP.NET MVC (por ejemplo), y tiene otros usos (una advertencia , tenga en cuenta también los pensamientos de Ayende si el nombre es un valor mágico en lugar de un llamador específico)


44
Esto también tiene problemas de interoperabilidad; no todos los idiomas admiten la creación de un tipo anónimo como ese sobre la marcha.
Brian

55
Me encanta cómo nadie realmente respondió la pregunta, en cambio, la gente ofrece un argumento de "esto es mejor". : p ¿Es un abuso o no?
Sam Saffron

77
Me interesaría ver la reacción de Eric Lippert ante esto. Porque está en el código MARCO . Y es igual de horrible.
Matt Hinze

26
No creo que el problema sea la legibilidad después de que se haya escrito el código. Creo que el verdadero problema es la capacidad de aprendizaje del código. ¿Qué vas a pensar cuando tu intellisense dice .Atributos (objeto obj) ? Tendrá que ir a leer la documentación (que nadie quiere hacer) porque no sabrá qué pasar al método. No creo que esto sea realmente mejor que el ejemplo en la pregunta.
NoDan

2
@Arnis, por qué es más flexible: no se basa en el nombre del parámetro implícito, lo que podría (no citarme) causar problemas con algunas implementaciones lambda (otros idiomas), pero también puede usar objetos regulares con propiedades definidas. Por ejemplo, podría tener una HtmlAttributesclase con los atributos esperados (para intellisense) e ignorar aquellos con nullvalores ...
Marc Gravell

137

Solo quería agregar mi opinión (soy el autor del componente de cuadrícula MvcContrib).

Esto definitivamente es abuso de lenguaje, sin duda. Sin embargo, realmente no lo consideraría contrario a la intuición: cuando miras una llamada Attributes(style => "width:100%", @class => "foo")
, creo que es bastante obvio lo que está sucediendo (ciertamente no es peor que el enfoque de tipo anónimo). Desde una perspectiva inteligente, estoy de acuerdo en que es bastante opaco.

Para aquellos interesados, alguna información de fondo sobre su uso en MvcContrib ...

Agregué esto a la cuadrícula como una preferencia personal: no me gusta el uso de tipos anónimos como diccionarios (tener un parámetro que toma "objeto" es tan opaco como uno que toma los parámetros Func []) y el inicializador de la colección Diccionario es más bien detallado (tampoco soy un fanático de las interfaces fluidas detalladas, por ejemplo, tener que encadenar varias llamadas a un atributo ("estilo", "display: none"). Atributo ("clase", "foo"), etc.)

Si C # tuviera una sintaxis menos detallada para los literales del diccionario, entonces no me habría molestado en incluir esta sintaxis en el componente de cuadrícula :)

También quiero señalar que el uso de esto en MvcContrib es completamente opcional: estos son métodos de extensión que envuelven sobrecargas que toman un IDictionary en su lugar. Creo que es importante que si proporciona un método como este, también debe admitir un enfoque más "normal", por ejemplo, para interoperabilidad con otros idiomas.

Además, alguien mencionó la 'sobrecarga de reflexión' y solo quería señalar que realmente no hay mucha sobrecarga con este enfoque: no hay una compilación de reflexión o expresión de tiempo de ejecución involucrada (ver http://blog.bittercoder.com /PermaLink,guid,206e64d1-29ae-4362-874b-83f5b103727f.aspx ).


1
También he tratado de abordar algunas de las cuestiones planteadas aquí con más profundidad en mi blog: jeremyskinner.co.uk/2009/12/02/lambda-abuse-the-mvccontrib-hash
Jeremy Skinner

44
No es menos opaco en Intellisense que los objetos anónimos.
Brad Wilson, el

22
+1 por mencionar que la interfaz se agrega a través de métodos de extensión opcionales . Los usuarios que no sean C # (y cualquier persona ofendida por el abuso del lenguaje) simplemente pueden abstenerse de usarlo.
Jørn Schou-Rode

50

Yo preferiría

Attributes.Add(string name, string value);

Es mucho más explícito y estándar y no se gana nada usando lambdas.


20
¿Sin embargo, lo es? html.Attributes.Add("style", "width:100%");no se lee tan bien como style = "width:100%"(el html real generado), mientras que style => "width:100%"está muy cerca de cómo se ve en el HTML resultante.
Jamie Penney el

66
Su sintaxis permite trucos como .Attributes (id => 'foo', @class => 'bar', style => 'width: 100%'). La firma de la función utiliza la sintaxis de params para un número variable de args: Atributos (params Func <object, object> [] args). Es muy poderoso, pero me llevó bastante tiempo entender qué sucede.
Remus Rusanu

27
@Jamie: Intentar hacer que el código C # se vea como el código HTML sería una mala razón para las decisiones de diseño. Son idiomas completamente diferentes para propósitos completamente diferentes, y no deberían tener el mismo aspecto.
Guffa el

17
¿Se podría haber utilizado un objeto anónimo sin sacrificar la "belleza"? .Atributos (nuevo {id = "foo", @class = "bar", style = "width: 100%"}) ??
Funka

10
@Guffa ¿Por qué sería una mala razón para tomar decisiones de diseño? ¿Por qué no deberían verse iguales? Por ese razonamiento, ¿deberían verse intencionalmente diferentes? No digo que estés equivocado, solo digo que quizás quieras elaborar tu punto más a fondo.
Samantha Branham el

45

Bienvenido a Rails Land :)

Realmente no tiene nada de malo, siempre y cuando sepas lo que está sucediendo. (Es cuando este tipo de cosas no está bien documentado que hay un problema).

La totalidad del marco Rails se basa en la idea de la convención sobre la configuración. Nombrar las cosas de una determinada manera te lleva a una convención que están usando y obtienes una gran cantidad de funciones de forma gratuita. Seguir la convención de nombres te lleva a donde vas más rápido. Todo funciona de manera brillante.

Otro lugar donde he visto un truco como este es en las aserciones de llamadas a métodos en Moq. Se pasa una lambda, pero la lambda nunca se ejecuta. Solo usan la expresión para asegurarse de que se realizó la llamada al método y lanzan una excepción si no.


3
Estaba un poco indeciso, pero estoy de acuerdo. Aparte de la sobrecarga de reflexión, no hay una diferencia significativa entre usar una cadena como en Add () versus usar un nombre de parámetro lambda. Al menos eso se me ocurre. Puede acumularlo y escribir "sytle" sin darse cuenta de ambas maneras.
Samantha Branham el

55
No podía entender por qué esto no era extraño para mí, y luego recordé a Rails. : D
Allyn

43

Esto es horrible en más de un nivel. Y no, esto no se parece en nada a Ruby. Es un abuso de C # y .NET.

Ha habido muchas sugerencias sobre cómo hacer esto de una manera más directa: tuplas, tipos anónimos, una interfaz fluida, etc.

Lo que lo hace tan malo es que es solo una manera de imaginarse por su propio bien:

  • ¿Qué sucede cuando necesita llamar a esto desde Visual Basic?

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

  • Es completamente contrario a la intuición, e intellisense proporcionará poca ayuda para descubrir cómo pasar cosas.

  • Es innecesariamente ineficiente.

  • Nadie tendrá idea de cómo mantenerlo.

  • ¿Cuál es el tipo de argumento que va a los atributos? ¿es Func<object,string>? ¿Cómo se revela esa intención? ¿Qué va a decir su documentación de Intellisense, "Por favor ignore todos los valores del objeto"?

Creo que está completamente justificado tener esos sentimientos de repulsión.


44
Yo diría que es completamente intuitivo. :)
Arnis Lapsa

44
Dices que no se parece en nada a Ruby. Pero es muchísimo parecido a la sintaxis de Ruby para especificar las claves y los valores de una tabla hash.
Charlie Flowers

3
¡Código que se rompe bajo conversión alfa! Yey!
Phil

3
@Charlie, sintácticamente se ve similar, semánticamente es muy diferente.
Sam Saffron

39

Estoy en el campo de "brillo de sintaxis", si lo documentan claramente, y se ve increíblemente genial, ¡casi no hay problema con eso!


10
Amén hermano. Amén (Amén 2º requeridos para cumplir longitud minutos para los comentarios :)
Charlie Flores

Tu comentario solo fue mucho más de lo necesario. Pero entonces, no puedes solo Amén una vez y luego poner tu comentario: D
Sleeper Smith

37

Ambos. Es abuso de expresiones lambda Y brillo de sintaxis.


16
Entonces, ¿es un abuso sintáctico brillante de las expresiones lambda? Creo que estoy de acuerdo :)
Seth Petry-Johnson

21

Casi nunca me encontré con este tipo de uso. Creo que es "inapropiado" :)

Esta no es una forma común de uso, es inconsistente con las convenciones generales. Este tipo de sintaxis tiene ventajas y desventajas, por supuesto:

Contras

  • El código no es intuitivo (las convenciones usuales son diferentes)
  • Tiende a ser frágil (cambiar el nombre del parámetro interrumpirá la funcionalidad).
  • Es un poco más difícil de probar (falsificar la API requerirá el uso de la reflexión en las pruebas).
  • Si la expresión se usa intensamente, será más lenta debido a la necesidad de analizar el parámetro y no solo el valor (costo de reflexión)

Pros

  • Es más legible después de que el desarrollador se ajustó a esta sintaxis.

En pocas palabras : en el diseño de API pública, habría elegido una forma más explícita.


2
@Elisha: tus ventajas y desventajas se invierten. Al menos espero que no estés diciendo que un Pro tiene un código "no intuitivo". ;-)
Metro Pitufo

Para este caso particular, el nombre del parámetro lambda y el parámetro de cadena son frágiles. Es como usar Dynamic para el análisis XML: es apropiado porque de todos modos no puede estar seguro acerca de XML.
Arnis Lapsa

19

No, ciertamente no es una práctica común. Es contrario a la intuición, no hay forma de mirar el código para descubrir qué hace. Tienes que saber cómo se usa para entender cómo se usa.

En lugar de suministrar atributos utilizando una matriz de delegados, los métodos de encadenamiento serían más claros y funcionarían mejor:

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

Aunque esto es un poco más para escribir, es claro e intuitivo.


66
De Verdad? Sabía exactamente qué pretendía ese fragmento de código cuando lo miré. No es tan obtuso a menos que seas muy estricto. Se podría dar el mismo argumento sobre la sobrecarga + para la concatenación de cadenas, y que siempre deberíamos usar un método Concat () en su lugar.
Samantha Branham el

44
@Stuart: No, no sabías exactamente, solo estabas adivinando en función de los valores utilizados. Cualquiera puede adivinar, pero adivinar no es una buena forma de entender el código.
Guffa el

11
Supongo que usar .Attribute("style", "width:100%")me da style="width:100%", pero por lo que sé, podría darme foooooo. No estoy viendo la diferencia.
Samantha Branham el

55
"Adivinar en función de los valores utilizados" es SIEMPRE lo que hace cuando mira el código. Si encuentra una llamada a stream.close (), asume que cierra una transmisión, pero también podría hacer algo completamente diferente.
Wouter Lievens

1
@Wouter: Si SIEMPRE está adivinando al leer el código, debe tener grandes dificultades para leer el código ... Si veo una llamada a un método llamado "cerrar", puedo deducir que el autor de la clase no sabe acerca de las convenciones de nomenclatura , así que dudaría en dar nada por sentado sobre lo que hace el método.
Guffa

17

¿Puedo usar esto para acuñar una frase?

magic lambda (n): una función lambda utilizada únicamente con el propósito de reemplazar una cadena mágica.


si ... eso es gracioso y tal vez sea algo mágico, en el sentido de que no hay seguridad en el tiempo de compilación, ¿hay algún lugar donde este uso pueda causar tiempo de ejecución en lugar de errores en tiempo de compilación?
Maslow


16

Todo este despotricar sobre la "horrible" es un montón de muchachos de C # que reaccionan de forma exagerada (y yo soy un programador de C # desde hace mucho tiempo y sigo siendo un gran admirador del lenguaje). No hay nada horrible en esta sintaxis. Es simplemente un intento de hacer que la sintaxis se parezca más a lo que está tratando de expresar. Cuanto menos "ruido" haya en la sintaxis de algo, más fácil será entenderlo el programador. Disminuir el ruido en una línea de código solo ayuda un poco, pero deja que eso se acumule en más y más código, y resulta ser un beneficio sustancial.

Este es un intento del autor de luchar por los mismos beneficios que los DSL le brindan: cuando el código "parece" lo que está tratando de decir, ha llegado a un lugar mágico. Puede debatir si esto es bueno para la interoperabilidad, o si es más agradable que los métodos anónimos para justificar parte del costo de "complejidad". Es justo ... así que en su proyecto debe tomar la decisión correcta de usar este tipo de sintaxis. Pero aún así ... este es un intento inteligente por parte de un programador de hacer lo que, al final del día, todos intentamos hacer (ya sea que nos demos cuenta o no). Y lo que todos intentamos hacer es esto: "Dígale a la computadora lo que queremos que haga en un lenguaje lo más cercano posible a cómo pensamos sobre lo que queremos que haga".

Acercarse a expresar nuestras instrucciones a las computadoras de la misma manera que pensamos internamente es una clave para hacer que el software sea más fácil de mantener y más preciso.

EDITAR: Dije "la clave para hacer que el software sea más fácil de mantener y más preciso", que es un poco exageradamente ingenuamente exagerado de unicornio. Lo cambié a "una clave".


12

Este es uno de los beneficios de los árboles de expresión: se puede examinar el código en sí para obtener información adicional. Así es como .Where(e => e.Name == "Jamie")se puede convertir en la cláusula SQL Where equivalente. Este es un uso inteligente de los árboles de expresión, aunque espero que no vaya más allá de esto. Es probable que cualquier cosa más compleja sea más difícil que el código que espera reemplazar, por lo que sospecho que será autolimitado.


Es un punto válido, pero cierto en la publicidad: LINQ viene con un conjunto completo de atributos como TableAttribute y ColumnAttribute que hacen de este un asunto más legítimo. Además, la asignación de linq analiza los nombres de clase y los nombres de propiedad, que son posiblemente más estables que los nombres de parámetros.
Remus Rusanu

Estoy de acuerdo contigo allí. He cambiado un poco mi postura sobre esto después de leer lo que Eric Lippert / Anders Helsberg / etc. tenía que decir al respecto. Pensé en dejar esta respuesta, ya que todavía es algo útil. Por lo que vale, ahora creo que este estilo de trabajar con HTML es agradable, pero no encaja con el lenguaje.
Jamie Penney el

7

Es un enfoque interesante. Si restringió el lado derecho de la expresión para que sean constantes solo entonces podría implementar usando

Expression<Func<object, string>>

Lo que creo que es lo que realmente quieres en lugar del delegado (estás usando la lambda para obtener nombres de ambos lados). Ver implementación ingenua a continuación:

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;
}

Esto incluso podría abordar la preocupación de interoperabilidad entre idiomas que se mencionó anteriormente en el hilo.


6

El código es muy inteligente, pero potencialmente causa más problemas que resuelve.

Como ha señalado, ahora hay una oscura dependencia entre el nombre del parámetro (estilo) y un atributo HTML. No se realiza la comprobación del tiempo de compilación. Si el nombre del parámetro está mal escrito, la página probablemente no tendrá un mensaje de error de tiempo de ejecución, pero será mucho más difícil de encontrar un error lógico (sin error, pero con un comportamiento incorrecto).

Una mejor solución sería tener un miembro de datos que se pueda verificar en tiempo de compilación. Entonces, en lugar de esto:

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

El compilador puede verificar el código con una propiedad Style:

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

o incluso:

.Attributes.Style.Width.Percent = 100;

Eso es más trabajo para los autores del código, pero este enfoque aprovecha la fuerte capacidad de verificación de tipos de C #, que ayuda a evitar que los errores entren en el código en primer lugar.


3
Agradezco la verificación en tiempo de compilación, pero creo que esto se reduce a una cuestión de opinión. Tal vez algo como los nuevos atributos () {Estilo: "ancho: 100%"} ganaría a más personas para esto, ya que es más conciso. Aún así, implementar todo lo que HTML permite es un trabajo enorme y no puedo culpar a alguien por solo usar cadenas / lambdas / clases anónimas.
Samantha Branham el

5

de hecho, parece Ruby =), al menos para mí el uso de un recurso estático para una "búsqueda" dinámica posterior no se ajusta a las consideraciones de diseño de la API, espero que este truco inteligente sea opcional en esa API.

Podríamos heredar de IDictionary (o no) y proporcionar un indexador que se comporte como una matriz php cuando no necesite agregar una clave para establecer un valor. Será un uso válido de la semántica de .net, no solo c #, y aún necesita documentación.

espero que esto ayude


4

En mi opinión es abuso de las lambdas.

En cuanto al brillo de sintaxis, me resulta style=>"width:100%"confuso. Particularmente por el =>lugar de=


4

En mi humilde opinión, es una forma genial de hacerlo. A todos nos encanta el hecho de que nombrar un controlador de clase lo convertirá en un controlador en MVC, ¿verdad? Entonces, hay casos en los que el nombre sí importa.

También la intención es muy clara aquí. Es muy fácil de entender que .Attribute( book => "something")resultará book="something"y .Attribute( log => "something")resultará enlog="something"

Supongo que no debería ser un problema si lo tratas como una convención. Soy de la opinión de que lo que sea que te haga escribir menos código y haga que la intención sea obvia es algo bueno.


44
Nombrar un controlador de clase no hará sentadillas si no heredas del controlador también ...
Jordan Wallwork

3

Si los nombres de los métodos (func) se eligen bien, esta es una manera brillante de evitar dolores de cabeza por mantenimiento (es decir: agregar un nuevo func, pero olvidó agregarlo a la lista de asignación de parámetros de función). Por supuesto, debe documentarlo en gran medida y será mejor que genere automáticamente la documentación de los parámetros a partir de la documentación para las funciones de esa clase ...


1

Creo que esto no es mejor que las "cuerdas mágicas". Tampoco soy muy fanático de los tipos anónimos para esto. Necesita un enfoque mejor y fuertemente tipado.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.