434 Stimmen

Reflektierende Parameternamen: Missbrauch von C#-Lambda-Ausdrücken oder brillante Syntax?

Ich betrachte die MvcContrib Grid-Komponente und ich bin fasziniert und gleichzeitig abgestoßen von einem syntaktischen Trick, der in der Gittersyntax :

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

Die obige Syntax setzt das style-Attribut des generierten HTML auf width:100% . Wenn Sie genau hinschauen, ist "Stil" nirgends angegeben. Er wird abgeleitet aus dem Name des Parameters im Ausdruck! Ich musste nachforschen und fand heraus, wo die "Magie" passiert:

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

Der Code verwendet also in der Tat den formalen Namen der Parameter zur Kompilierungszeit, um das Wörterbuch der Attribut-Name-Wert-Paare zu erstellen. Das daraus resultierende Syntaxkonstrukt ist in der Tat sehr ausdrucksstark, aber gleichzeitig auch sehr gefährlich.

Die allgemeine Verwendung von Lambda-Ausdrücken ermöglicht die Ersetzung der Namen ohne Nebenwirkung verwendet werden. Ich sehe ein Beispiel in einem Buch, das sagt collection.ForEach(book => Fire.Burn(book)) Ich weiß, dass ich in meinen Code schreiben kann collection.ForEach(log => Fire.Burn(log)) y es bedeutet dasselbe . Aber mit der MvcContrib Grid-Syntax finde ich plötzlich Code, der aktiv nach den Namen sucht und Entscheidungen trifft, die ich für meine Variablen wähle!

Also ist dies gängige Praxis mit der C# 3.5/4.0-Gemeinschaft und die Lambda-Ausdrücke Liebhaber? Oder handelt es sich um einen abtrünnigen Außenseiter, über den ich mir keine Sorgen machen sollte?

10 Stimmen

+1 für die Einführung der Hash-Rakete in C#

2 Stimmen

Foreach (var func in hash) ändern in: Array.ForEarch(hash, func => Add(func.Method.GetParameters()[0].Name, func(null)));

34 Stimmen

Ich würde behaupten, dass dies offensichtlich ist, solange man bereit ist, die Absicht des Codes zu betrachten und nicht nur die Syntax zu analysieren. Wenn Sie guten Code lesen, sollten Sie das ohnehin tun. Die Syntax ist nur ein Vehikel für die Intention, und ich würde behaupten, dass dies ein Code ist, der die Intention offenbart.

153voto

Marc Gravell Punkte 970173

Ich finde das merkwürdig, nicht so sehr wegen der Name sondern weil die lambda ist unnötig ; es könnte einen anonymen Typ verwenden und flexibler sein:

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

Dieses Muster wird zum Beispiel in ASP.NET MVC verwendet, und hat andere Anwendungen (a caveat beachten Sie auch Gedanken von Ayende wenn der Name ein magischer Wert und nicht aufruferspezifisch ist)

4 Stimmen

Dies hat auch Interop-Probleme; nicht alle Sprachen unterstützen die Erstellung eines solchen anonymen Typs im laufenden Betrieb.

5 Stimmen

Ich finde es toll, dass niemand wirklich auf die Frage geantwortet hat, sondern stattdessen ein "das ist besser"-Argument vorgebracht hat :p Ist es ein Missbrauch oder nicht?

1 Stimmen

Es geht nur um die Lesbarkeit. Dieser Ansatz beinhaltet mehr geschweifte Klammern und new Stichwort. Könnten Sie einige Überlegungen anstellen, warum dies (unter Verwendung des Anonymitätstyps) more flexible ?

149voto

Brian Punkte 115257

Die Interoperabilität ist schlecht. Betrachten Sie zum Beispiel dieses C# - F# Beispiel

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")

Ergebnis:

"arg" wird gedruckt (nicht "yadda").

Infolgedessen sollten Bibliotheksentwickler entweder diese Art von "Missbrauch" vermeiden oder zumindest eine "Standard"-Überladung (z. B. die den Stringnamen als zusätzlichen Parameter annimmt) bereitstellen, wenn sie eine gute Interoperabilität zwischen den .Net-Sprachen erreichen wollen.

0 Stimmen

Das hatte ich nicht bedacht. Wie würde man den Ausdruck Baum korrekt zugreifen, damit es in der F# Fall zu arbeiten?

11 Stimmen

Sie haben keine. Diese Strategie ist einfach nicht tragbar. (Falls es hilft, als Beispiel in die andere Richtung, könnte F# in der Lage sein, Methoden zu überladen, die sich nur im Rückgabetyp unterscheiden (Typinferenz kann das tun). Dies kann in der CLR ausgedrückt werden. Aber F# verbietet es, vor allem weil diese APIs dann nicht von C# aus aufrufbar wären). Wenn es um Interop geht, gibt es immer Kompromisse bei "Rand"-Funktionen in Bezug auf die Vorteile, die Sie gegen die Interop, die Sie eintauschen.

0 Stimmen

Das scheint ein bisschen wie ein Versehen auf der F#-Sprache Seite -> gibt es einen guten Grund, warum dies der Fall ist? Ich vermute, Lambda-Ausdrücke in C# und Funktionen in F# erzeugen unterschiedliche IL.

137voto

Jeremy Skinner Punkte 1341

Ich wollte nur meine Meinung dazu sagen (ich bin der Autor der Grid-Komponente MvcContrib).

Das ist eindeutig Sprachmissbrauch - daran besteht kein Zweifel. Allerdings würde ich es nicht wirklich als kontraintuitiv betrachten - wenn Sie sich einen Aufruf von Attributes(style => "width:100%", @class => "foo")
Ich denke, es ist ziemlich offensichtlich, was hier vor sich geht (es ist sicherlich nicht schlimmer als der anonyme Ansatz). Von einem intellisense Perspektive, ich stimme es ist ziemlich undurchsichtig.

Für diejenigen, die es interessiert, einige Hintergrundinformationen über die Verwendung in MvcContrib...

Ich fügte dies in das Raster als eine persönliche Vorliebe - Ich mag nicht die Verwendung von anonymen Typen als Wörterbücher (mit einem Parameter, der "Objekt" nimmt ist genauso undurchsichtig wie eine, die Parameter Func[] nimmt) und die Dictionary-Auflistung Initialisierer ist ziemlich wortreich (ich bin auch kein Fan von wortreichen fließenden Schnittstellen, z. B. mit mehreren Aufrufen zu einem Attribut("style", "display:none").Attribut("class", "foo") usw. zu verketten)

Wenn C# eine weniger ausführliche Syntax für Wörterbuchliterale hätte, dann hätte ich mir nicht die Mühe gemacht, diese Syntax in die Gitterkomponente einzubauen :)

Ich möchte auch darauf hinweisen, dass die Verwendung dieser in MvcContrib ist völlig optional - diese sind Erweiterung Methoden, die Überladungen, die ein IDictionary stattdessen nehmen wrap. Ich denke, es ist wichtig, dass Sie, wenn Sie eine Methode wie diese bereitstellen, auch einen "normaleren" Ansatz unterstützen sollten, z. B. für Interop mit anderen Sprachen.

Außerdem erwähnte jemand den "Reflexions-Overhead", und ich wollte nur darauf hinweisen, dass es bei diesem Ansatz wirklich keinen großen Overhead gibt - es ist keine Reflexion zur Laufzeit oder Ausdruckskompilierung beteiligt (siehe http://blog.bittercoder.com/PermaLink,guid,206e64d1-29ae-4362-874b-83f5b103727f.aspx ).

2 Stimmen

Ich habe auch versucht, einige der hier aufgeworfenen Fragen in meinem Blog etwas ausführlicher zu behandeln: jeremyskinner.co.uk/2009/12/02/lambda-abuse-the-mvccontrib-hash

4 Stimmen

Es ist in Intellisense nicht weniger undurchsichtig als anonyme Objekte.

23 Stimmen

+1 für die Erwähnung, dass die Schnittstelle über optional Erweiterungsmethoden. Nicht-C#-Benutzer (und alle, die sich am Sprachmissbrauch stören) können einfach auf die Verwendung verzichten.

49voto

NotDan Punkte 30763

Ich würde bevorzugen

Attributes.Add(string name, string value);

Es ist viel eindeutiger und standardisiert, und durch die Verwendung von Lambdas wird nichts gewonnen.

20 Stimmen

Ist es das wirklich? html.Attributes.Add("style", "width:100%"); liest sich nicht so schön wie style = "width:100%" (die tatsächlich erzeugte HTML), während style => "width:100%" kommt dem Aussehen des resultierenden HTML-Satzes sehr nahe.

6 Stimmen

Ihre Syntax erlaubt Tricks wie .Attribute(id=>'foo', @class=>'bar', style=>'width:100%'). Die Funktionssignatur verwendet die params-Syntax für eine variable Anzahl von Argumenten: Attribute(params Func<Objekt, Objekt>[] args). Diese Funktion ist sehr mächtig, aber ich brauchte eine ganze Weile um zu verstehen, was sie tut.

28 Stimmen

@Jamie: Der Versuch, den C#-Code wie den HTML-Code aussehen zu lassen, wäre ein schlechter Grund für Design-Entscheidungen. Es sind völlig unterschiedliche Sprachen für völlig unterschiedliche Zwecke, und sie sollten nicht gleich aussehen.

45voto

Jason Punyon Punkte 38137

Willkommen im Rails Land :)

Daran ist wirklich nichts auszusetzen, solange man weiß, was vor sich geht. (Problematisch wird es dann, wenn diese Dinge nicht gut dokumentiert sind).

Die Gesamtheit des Rails-Frameworks basiert auf der Idee, dass Konvention vor Konfiguration geht. Wenn man Dinge auf eine bestimmte Art und Weise benennt, wird man in eine Konvention eingebunden, die sie verwenden, und man erhält eine ganze Menge Funktionalität umsonst. Wenn man die Namenskonvention befolgt, kommt man schneller ans Ziel. Das Ganze funktioniert brillant.

Ein weiterer Ort, an dem ich einen solchen Trick gesehen habe, sind Methodenaufruf-Assertions in Moq. Sie übergeben ein Lambda, aber das Lambda wird nie ausgeführt. Sie verwenden den Ausdruck nur, um sicherzustellen, dass der Methodenaufruf erfolgt ist, und lösen andernfalls eine Ausnahme aus.

3 Stimmen

Ich habe ein wenig gezögert, aber ich stimme zu. Abgesehen vom Reflection-Overhead gibt es keinen signifikanten Unterschied zwischen der Verwendung einer Zeichenkette wie in Add() und der Verwendung eines Lambda-Parameternamens. Zumindest kann ich mir das vorstellen. Man kann es vermasseln und "sytle" eingeben, ohne beides zu bemerken.

5 Stimmen

Ich konnte mir nicht erklären, warum mir das nicht komisch vorkam, und dann fiel mir Rails ein :D.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X