Ich habe mir kürzlich F # angesehen, und obwohl ich wahrscheinlich nicht bald über den Zaun springen werde, werden definitiv einige Bereiche hervorgehoben, in denen C # (oder Bibliotheksunterstützung) das Leben erleichtern könnte.
Insbesondere denke ich über die Musteranpassungsfähigkeit von F # nach, die eine sehr reichhaltige Syntax ermöglicht - viel aussagekräftiger als die aktuellen Schalter- / bedingten C # -Äquivalente. Ich werde nicht versuchen, ein direktes Beispiel zu geben (meine F # ist nicht dazu in der Lage), aber kurz gesagt, es erlaubt:
- Übereinstimmung nach Typ (mit vollständiger Überprüfung auf diskriminierte Gewerkschaften) [Beachten Sie, dass dies auch den Typ für die gebundene Variable ableitet und den Mitgliedern Zugriff gewährt usw.]
- Übereinstimmung nach Prädikat
- Kombinationen der oben genannten (und möglicherweise einige andere Szenarien, die mir nicht bekannt sind)
Während es für C # schön wäre, irgendwann etwas von diesem Reichtum auszuleihen, habe ich mir in der Zwischenzeit angesehen, was zur Laufzeit getan werden kann - zum Beispiel ist es ziemlich einfach, einige Objekte zusammenzuschlagen, um Folgendes zu ermöglichen:
var getRentPrice = new Switch<Vehicle, int>()
.Case<Motorcycle>(bike => 100 + bike.Cylinders * 10) // "bike" here is typed as Motorcycle
.Case<Bicycle>(30) // returns a constant
.Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20)
.Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20)
.ElseThrow(); // or could use a Default(...) terminator
Dabei ist getRentPrice ein Func <Vehicle, int>.
[Anmerkung - vielleicht ist Switch / Case hier die falsche Bezeichnung ... aber es zeigt die Idee]
Für mich ist dies viel klarer als das Äquivalent mit wiederholtem if / else oder einer zusammengesetzten ternären Bedingung (die für nicht triviale Ausdrücke sehr chaotisch wird - Klammern in Hülle und Fülle). Es vermeidet auch viel Casting und ermöglicht eine einfache Erweiterung (entweder direkt oder über Erweiterungsmethoden) auf spezifischere Übereinstimmungen, z. B. eine InRange-Übereinstimmung (...), die mit der VB Select ... -Fall "x bis y" vergleichbar ist " Verwendung.
Ich versuche nur zu beurteilen, ob die Leute glauben, dass Konstrukte wie die oben genannten von großem Nutzen sind (ohne Sprachunterstützung).
Beachten Sie außerdem, dass ich mit 3 Varianten der oben genannten gespielt habe:
- eine Func <TSource, TValue> -Version zur Auswertung - vergleichbar mit zusammengesetzten ternären bedingten Anweisungen
- eine Action <TSource> -Version - vergleichbar mit if / else if / else if / else if / else
- eine Expression <Func <TSource, TValue >> -Version - als erste, aber von beliebigen LINQ-Anbietern verwendbar
Darüber hinaus ermöglicht die Verwendung der Ausdrucks-basierten Version das Umschreiben des Ausdrucksbaums, wobei im Wesentlichen alle Zweige zu einem einzigen zusammengesetzten bedingten Ausdruck zusammengefasst werden, anstatt einen wiederholten Aufruf zu verwenden. Ich habe es in letzter Zeit nicht überprüft, aber in einigen frühen Entity Framework-Builds erinnere ich mich anscheinend daran, dass dies notwendig ist, da InvocationExpression nicht besonders gut gefallen hat. Es ermöglicht auch eine effizientere Verwendung mit LINQ-to-Objects, da wiederholte Delegatenaufrufe vermieden werden. Tests zeigen, dass eine Übereinstimmung wie oben (unter Verwendung des Ausdrucksformulars) mit derselben Geschwindigkeit [tatsächlich geringfügig schneller] im Vergleich zum entsprechenden C # ausgeführt wird zusammengesetzte bedingte Anweisung. Der Vollständigkeit halber dauerte die auf Func <...> basierende Version viermal so lange wie die C # -bedingte Anweisung, ist jedoch immer noch sehr schnell und in den meisten Anwendungsfällen wahrscheinlich kein großer Engpass.
Ich freue mich über alle Gedanken / Eingaben / Kritik / usw. zu den oben genannten Themen (oder zu den Möglichkeiten einer umfassenderen Unterstützung der C # -Sprache ... hier ist die Hoffnung ;-p).
quelle
switch-case
Erklärung. Versteh mich nicht falsch, ich denke, es hat seinen Platz und ich werde wahrscheinlich nach einer Möglichkeit suchen, es umzusetzen.Antworten:
Ich weiß, dass es ein altes Thema ist, aber in c # 7 können Sie Folgendes tun:
quelle
Nachdem ich versucht habe, solche "funktionalen" Dinge in C # zu tun (und sogar ein Buch darüber zu versuchen), bin ich zu dem Schluss gekommen, dass solche Dinge mit wenigen Ausnahmen nicht allzu viel helfen.
Der Hauptgrund ist, dass Sprachen wie F # einen großen Teil ihrer Leistung erhalten, wenn sie diese Funktionen wirklich unterstützen. Nicht "du kannst es schaffen", sondern "es ist einfach, es ist klar, es wird erwartet".
Beim Pattern Matching wird Ihnen beispielsweise vom Compiler mitgeteilt, ob eine unvollständige Übereinstimmung vorliegt oder wann eine andere Übereinstimmung niemals getroffen wird. Dies ist bei offenen Typen weniger nützlich, aber wenn eine diskriminierte Vereinigung oder Tupel gefunden werden, ist es sehr geschickt. In F # erwarten Sie, dass Personen Musterübereinstimmungen vornehmen, und dies ist sofort sinnvoll.
Das "Problem" ist, dass es natürlich ist, fortfahren zu wollen, sobald Sie anfangen, einige Funktionskonzepte zu verwenden. Die Nutzung von Tupeln, Funktionen, partieller Methodenanwendung und -currying, Mustervergleich, verschachtelten Funktionen, Generika, Monadenunterstützung usw. in C # wird jedoch sehr schnell sehr hässlich. Es macht Spaß und einige sehr kluge Leute haben einige sehr coole Dinge in C # gemacht, aber es fühlt sich schwer an , es tatsächlich zu benutzen .
Was ich in C # häufig (projektübergreifend) verwendet habe:
** Beachten Sie jedoch: Das Fehlen einer automatischen Verallgemeinerung und Typinferenz behindert die Verwendung selbst dieser Funktionen erheblich. ** **.
All dies sagte, wie jemand anderes in einem kleinen Team für einen bestimmten Zweck erwähnte, ja, vielleicht können sie helfen, wenn Sie mit C # nicht weiterkommen. Aber meiner Erfahrung nach fühlten sie sich normalerweise stressiger an, als sie es wert waren - YMMV.
Einige andere Links:
quelle
Der Grund, warum C # das Einschalten des Typs nicht einfach macht, liegt wahrscheinlich darin, dass es sich in erster Linie um eine objektorientierte Sprache handelt. Der "richtige" Weg, dies objektorientiert zu tun, besteht darin, eine GetRentPrice-Methode für Vehicle und zu definieren Überschreiben Sie es in abgeleiteten Klassen.
Trotzdem habe ich ein bisschen Zeit damit verbracht, mit Multiparadigmen und funktionalen Sprachen wie F # und Haskell zu spielen, die diese Art von Fähigkeit haben, und ich bin auf eine Reihe von Stellen gestoßen, an denen es vorher nützlich gewesen wäre (z. B. wenn Sie schreiben nicht die Typen, die Sie zum Einschalten benötigen, damit Sie keine virtuelle Methode auf sie implementieren können) und ich würde dies zusammen mit diskriminierten Gewerkschaften in der Sprache begrüßen.
[Bearbeiten: Teil über die Leistung entfernt, da Marc angedeutet hat, dass er kurzgeschlossen werden könnte]
Ein weiteres potenzielles Problem ist die Benutzerfreundlichkeit. Aus dem letzten Aufruf geht hervor, was passiert, wenn das Spiel keine Bedingungen erfüllt. Wie verhält es sich jedoch, wenn es zwei oder mehr Bedingungen erfüllt? Sollte es eine Ausnahme auslösen? Sollte es das erste oder das letzte Spiel zurückgeben?
Eine Möglichkeit, diese Art von Problem zu lösen, besteht darin, ein Wörterbuchfeld mit dem Typ als Schlüssel und dem Lambda als Wert zu verwenden, was mit der Objektinitialisierersyntax ziemlich knapp zu konstruieren ist. Dies berücksichtigt jedoch nur den konkreten Typ und lässt keine zusätzlichen Prädikate zu, sodass es möglicherweise nicht für komplexere Fälle geeignet ist. [Randnotiz - Wenn Sie sich die Ausgabe des C # -Compilers ansehen, werden switch-Anweisungen häufig in wörterbuchbasierte Sprungtabellen konvertiert. Es scheint also keinen guten Grund zu geben, warum das Einschalten von Typen nicht unterstützt werden kann.]
quelle
Ich denke nicht, dass diese Art von Bibliotheken (die sich wie Spracherweiterungen verhalten) eine breite Akzeptanz finden, aber es macht Spaß, mit ihnen zu spielen, und sie können für kleine Teams, die in bestimmten Bereichen arbeiten, in denen dies nützlich ist, sehr nützlich sein. Wenn Sie zum Beispiel Tonnen von 'Geschäftsregeln / Logik' schreiben, die beliebige Typprüfungen wie diese und so weiter durchführen, kann ich sehen, wie praktisch es wäre.
Ich habe keine Ahnung, ob dies jemals eine C # -Sprachenfunktion sein dürfte (scheint zweifelhaft, aber wer kann die Zukunft sehen?).
Als Referenz ist das entsprechende F # ungefähr:
Angenommen, Sie hätten eine Klassenhierarchie nach dem Vorbild von definiert
quelle
Um Ihre Frage zu beantworten: Ja, ich denke, syntaktische Konstrukte mit Mustervergleich sind nützlich. Ich würde gerne syntaktische Unterstützung in C # dafür sehen.
Hier ist meine Implementierung einer Klasse, die (fast) dieselbe Syntax bietet, die Sie beschreiben
Hier ist ein Testcode:
quelle
Mustervergleich (wie hier beschrieben ) dient dazu, Werte gemäß ihrer Typspezifikation zu dekonstruieren. Das Konzept einer Klasse (oder eines Typs) in C # stimmt jedoch nicht mit Ihnen überein.
Es ist nichts Falsches am Multi-Paradigma-Sprachdesign, im Gegenteil, es ist sehr schön, Lambdas in C # zu haben, und Haskell kann z. B. IO zwingende Dinge tun. Aber es ist keine sehr elegante Lösung, nicht in Haskell-Manier.
Da jedoch sequentielle prozedurale Programmiersprachen als Lambda-Kalkül verstanden werden können und C # zufällig gut in die Parameter einer sequentiellen prozeduralen Sprache passt, ist dies eine gute Anpassung. Wenn Sie jedoch etwas aus dem rein funktionalen Kontext von beispielsweise Haskell nehmen und dieses Merkmal dann in eine Sprache umwandeln, die nicht rein ist, wird dies kein besseres Ergebnis garantieren.
Mein Punkt ist, dass das, was Pattern Matching Tick macht, an das Sprachdesign und das Datenmodell gebunden ist. Trotzdem glaube ich nicht, dass Pattern Matching ein nützliches Merkmal von C # ist, da es weder typische C # -Probleme löst noch gut in das imperative Programmierparadigma passt.
quelle
IMHO ist die OO-Methode, solche Dinge zu tun, das Besuchermuster. Die Methoden Ihrer Besuchermitglieder fungieren einfach als Fallkonstrukte, und Sie lassen die Sprache selbst den entsprechenden Versand abwickeln, ohne die Typen "einsehen" zu müssen.
quelle
Obwohl es nicht sehr "C-scharf" ist, den Typ einzuschalten, weiß ich, dass das Konstrukt im allgemeinen Gebrauch ziemlich hilfreich wäre - ich habe mindestens ein persönliches Projekt, das es verwenden könnte (obwohl es überschaubar ist). Gibt es ein großes Problem mit der Kompilierungsleistung, wenn der Ausdrucksbaum neu geschrieben wird?
quelle
Ich denke, das sieht wirklich interessant aus (+1), aber eines ist zu beachten: Der C # -Compiler ist ziemlich gut darin, switch-Anweisungen zu optimieren. Nicht nur für Kurzschlüsse - Sie erhalten eine völlig andere IL, je nachdem, wie viele Fälle Sie haben und so weiter.
Ihr spezielles Beispiel macht etwas, das ich sehr nützlich finde - es gibt keine Syntax, die Groß- und Kleinschreibung nach Typ entspricht, wie zum Beispiel)
typeof(Motorcycle)
keine Konstante ist.Dies wird in dynamischen Anwendungen interessanter - Ihre Logik hier könnte leicht datengesteuert sein und eine Ausführung im Stil einer Regel-Engine ermöglichen.
quelle
Sie können das erreichen, wonach Sie suchen, indem Sie eine von mir geschriebene Bibliothek namens OneOf verwenden
Der Hauptvorteil gegenüber
switch
(undif
undexceptions as control flow
) besteht darin, dass es zur Kompilierungszeit sicher ist - es gibt keinen Standardhandler oder DurchfallEs ist auf Nuget und zielt auf net451 und netstandard1.6 ab
quelle