Nombre del parámetro que refleja: abuso de expresiones Lambda C # o brillo de syntax?

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

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

La syntax 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 encontrar dónde pasa la ‘magia’:

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

De hecho, el código usa el nombre formal de tiempo de comstackción para crear el diccionario de pares de nombre-valor de atributo. La construcción de syntax resultante es muy expresiva, pero al mismo tiempo muy peligrosa. El uso general de las expresiones lambda permite la sustitución de los nombres utilizados sin efectos secundarios. Veo un ejemplo en un libro que dice collection.ForEach(book => Fire.Burn(book)) Sé que puedo escribir en mi collection.ForEach(log => Fire.Burn(log)) y significa lo mismo cosa . Pero con la syntax MvcContrib Grid aquí, de repente, encuentro un código que se ve 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 delincuente rebelde un truco que no debería preocuparse?

    Esto tiene una interoperabilidad pobre. Por ejemplo, considere este ejemplo C # – F #

    DO#:

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

    F#:

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

    Resultado:

    “arg” está impreso (no “yadda”).

    Como resultado, los diseñadores de bibliotecas deberían 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 en los lenguajes .Net.

    Me parece extraño no tanto por el nombre , sino porque el lambda es innecesario ; 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 específico de la persona que llama)

    Solo quería compartir mi opinión (soy el autor del componente de grilla MvcContrib).

    Esto definitivamente es abuso de lenguaje, no hay duda al respecto. Sin embargo, realmente no lo consideraría contra intuitivo, cuando miras una llamada a 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 intellisense, estoy de acuerdo que es bastante opaco.

    Para aquellos interesados, algunos antecedentes sobre su uso en MvcContrib …

    Agregué esto a la grilla como preferencia personal – No me gusta el uso de tipos anónimos como diccionarios (tener un parámetro que tome “objeto” es tan opaco como uno que toma param Funcs []) y el inicializador de la colección de diccionarios es bastante detallado (tampoco soy fanático de las interfaces fluidas y verbosas, por ejemplo, tener que encadenar varias llamadas a un atributo (“estilo”, “pantalla: ninguna”). Atributo (“clase”, “foo”), etc.)

    Si C # tuviera una syntax menos verbosa para los literales del diccionario, entonces no me hubiera molestado incluir esta syntax en el componente de la 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 proporcionas un método como este, también debas apoyar un enfoque más “normal”, por ejemplo, para la interoperabilidad con otros idiomas.

    Además, alguien mencionó la ‘sobrecarga de reflexión’ y solo quería señalar que no hay mucho sobrecarga con este enfoque: no hay reflexión de tiempo de ejecución ni comstackción de expresiones involucradas (ver http://blog.bittercoder.com /PermaLink,guid,206e64d1-29ae-4362-874b-83f5b103727f.aspx ).

    Yo preferiría

     Attributes.Add(string name, string value); 

    Es mucho más explícito y estándar y no se gana nada con el uso de lambdas.

    Bienvenido a Rails Land 🙂

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

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

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

    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 una manera de imaginar para su propio bien:

    • ¿Qué sucede cuando necesitas llamar esto desde VB?

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

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

    • Es innecesariamente ineficiente.

    • Nadie sabrá cómo mantenerlo.

    • ¿Cuál es el tipo de argumento para los atributos, es Func ? ¿Cómo es esa intención reveladora? ¿Cuál es su documentación intellisense va a decir, “Por favor, ignore todos los valores de objeto”

    Creo que estás completamente justificado teniendo esos sentimientos de repugnancia.

    Estoy en el campo de “shinyz sintáctica”, si lo documentan claramente, y se ve increíblemente genial, ¡casi no hay problema con eso!

    Ambos. Es un abuso de expresiones lambda Y shinyz de syntax.

    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 syntax tiene sus pros y sus contras, por supuesto:

    Contras

    • El código no es intuitivo (las convenciones usuales son diferentes)
    • Tiende a ser frágil (el cambio de nombre del parámetro romperá 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 de manera intensiva, 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 syntax.

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

    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 proporcionar atributos usando una matriz de delegates, los métodos de encadenamiento serían más claros y funcionarían mejor:

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

    Aunque es un poco más difícil de escribir, es claro e intuitivo.

    Todo esto despotricando sobre la “horridness” es un grupo de muchachos c # de mucho tiempo exagerando (y soy un progtwigdor de C # desde hace mucho tiempo y todavía soy un gran fanático del lenguaje). No hay nada horrible sobre esta syntax. Es simplemente un bash de hacer que la syntax se parezca más a lo que estás tratando de express. Cuanto menos ruido hay en la syntax de algo, más fácil lo puede entender el progtwigdor. Disminuir el ruido en una línea de código solo ayuda un poco, pero deja que se acumule en más y más código, y resulta ser un beneficio sustancial.

    Este es un bash del autor de esforzarse por obtener los mismos beneficios que los DSL: cuando el código simplemente “se parece” a 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 bastante más agradable que los métodos anónimos para justificar parte del costo de “complejidad”. Bastante … así que en su proyecto debe tomar la decisión correcta de si usar este tipo de syntax. Pero aún así … este es un inteligente bash de un progtwigdor de hacer lo que, al final del día, todos estamos tratando de hacer (nos demos cuenta o no). Y lo que estamos tratando de hacer, es esto: “Díganle a la computadora lo que queremos que haga en un lenguaje que esté lo más cerca posible de cómo pensamos sobre lo que queremos que haga”.

    Acercarnos a express 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: Yo había dicho “la clave para hacer que el software sea más fácil de mantener y más preciso”, lo cual es un poco ingenuamente exagerado de unicornio. Lo cambié a “una clave”.

    ¿Qué pasa con lo siguiente?

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

    ¿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.

    Este es uno de los beneficios de los árboles de expresión: uno 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. Cualquier cosa más compleja es probable que sea más difícil que el código que espera reemplazar, por lo que sospecho que será autolimitante.

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

     Expression> 

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

     public static IDictionary Hash(params Expression>[] hash) { Dictionary values = new Dictionary(); 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 interoperativa de lenguaje cruzado que se mencionó anteriormente en el hilo.

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

    Como ha señalado, ahora existe una dependencia poco clara entre el nombre del parámetro (estilo) y un atributo HTML. No se realiza ninguna comprobación en tiempo de comstackció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 una falla lógica mucho más difícil de encontrar (sin error, pero con un comportamiento incorrecto).

    Una mejor solución sería tener un miembro de datos que pueda verificarse en tiempo de comstackción. Entonces en vez de esto:

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

    el código con una propiedad Style podría ser verificado por el comstackdor:

     .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 sólida capacidad de comprobación de tipos de C #, que ayuda a evitar que los errores entren en el código en primer lugar.

    de hecho, parece que 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 ingenioso truco 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 semántica .net no solo c #, y aún necesita documentación.

    espero que esto ayude

    En mi humilde opinión, es una buena manera 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 nombramiento sí importa.

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

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

    En mi opinión, es un abuso de las lambdas.

    En cuanto al brillo de la syntax, encuentro style=>"width:100%" plain confusing. Particularmente debido a => lugar de =

    Si los nombres del método (func) están bien seleccionados, entonces esta es una manera shiny de evitar dolores de cabeza de mantenimiento (es decir, agregar un nuevo func, pero se olvidó de agregarlo a la lista de mapeo de parámetros de funciones). Por supuesto, necesita documentarlo mucho y será mejor que genere automáticamente la documentación de los parámetros de la documentación para las funciones de esa clase …

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