Frage

Ich habe vor kurzem bei F # gesucht, und während ich nicht wahrscheinlich bin den Zaun in absehbarer Zeit zu springen, hebt es auf jeden Fall einige Bereiche, in denen C # (oder Bibliotheksunterstützung) könnte das Leben leichter machen.

Insbesondere denke ich über die Pattern-Matching-Fähigkeit von F #, die eine sehr reichen Syntax erlaubt - viel ausdrucksvoller als die Stromschalter / bedingten C # Äquivalente. Ich werde versuchen, kein direktes Beispiel (mein F # ist nicht bis zu ihm), aber kurz gesagt, um ihn erlaubt:

  • Spiel nach Typ (mit Vollabdeckung für diskriminierte Gewerkschaften Kontrolle) [beachten Sie dies auch den Typ für die gebundene Variable folgert, geben Mitglied Zugang etc]
  • Spiel von Prädikat
  • Kombinationen der oben genannten (und möglicherweise einige andere Szenarien Ich bin nicht bewusst)

Während es wäre schön für C #, um schließlich zu borgen [ähem] einige dieser Reichtum, in der Zwischenzeit ich habe geschaut, was zur Laufzeit durchgeführt werden - zum Beispiel, ist es ziemlich einfach, einige Objekte zu klopfen zusammen erlauben:

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

wo getRentPrice ist eine Func .

[Anmerkung - vielleicht Schalter / Gehäuse ist hier die falschen Begriffe ... aber es zeigt die Idee]

Für mich ist dies viel klarer als die äquivalent unter Verwendung von if / else oder einem Verbunddreifach konditionale (die für nicht-triviale Ausdrücke sehr chaotisch bekommt - Klammern in Hülle und Fülle). Es vermeidet auch ein Los Gießen und ermöglicht eine einfache Erweiterung (entweder direkt oder über Erweiterungsmethoden), um mehr spezifischen Streichhölzer, zum Beispiel eines InRange (...) Spiel vergleichbar mit dem VB Select. ..Case "x zu y" Nutzung.

Ich versuche nur zu messen, wenn die Menschen dort denken von Konstrukten viel Nutzen wie die oben ist (in Abwesenheit von Sprachunterstützung)?

Hinweis zusätzlich, dass ich habe mit drei Varianten des oben spielen:

  • eine Func Version zur Auswertung - vergleichbar mit Verbund ternären bedingten Anweisungen
  • eine Aktion Version - vergleichbar mit if / else if / else if / else if / else
  • eine Expression > Version - wie die erste, aber brauchbar durch willkürliche LINQ-Provider

Zusätzlich mit der Expression-basierten Version ermöglicht Expression Baum Umschreiben, im Wesentlichen all Zweige in einen einzigen zusammengesetzten bedingten Ausdruck inlining, anstatt wiederholt Aufruf verwenden. Ich habe in letzter Zeit nicht, aber in einigen frühen Entity Framework geprüft baut ich scheinen diese notwendig, daran zu erinnern, da es nicht InvocationExpression mochte sehr viel. Es ermöglicht auch eine effizientere Nutzung mit LINQ-to-Objekten, da es Delegaten Anrufungen wiederholt vermeidet - Tests zeigen eine Übereinstimmung wie die oben (das Expression Formular) mit der gleichen Geschwindigkeit durchführte [geringfügig schneller, in der Tat] im Vergleich zu dem äquivalenten C # Verbund bedingte Anweisung. Zur Vollständigkeit der Func <...> Basis-Version hat 4-mal so lang wie die C # bedingte Anweisung, ist aber immer noch sehr schnell und ist unwahrscheinlich, ein wichtiger Engpass in den meisten Anwendungsfällen sein.

Ich begrüße jeden Gedanken / input / Kritik / etc auf dem oben (oder über die Möglichkeiten reichen Sprache C # Unterstützung ... hier hofft ;-P).

War es hilfreich?

Lösung

Ich weiß, es ist ein altes Thema, aber in c # 7 Sie tun können:

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}

Andere Tipps

Bart De Smet hat eine 8-teilige Serie auf genau das tun, was du beschreibst. Finden Sie den ersten Teil hier .

Nach dem Versuch, solche „functional“ Dinge in C # zu tun (und sogar ein Buch über sie versucht), habe ich zu dem Schluss gekommen, dass keine, mit wenigen Ausnahmen, wie die Dinge nicht zu viel helfen.

Der Hauptgrund dafür ist, dass Sprachen wie F # eine Menge ihrer Macht bekommen von wirklich diese Funktionen unterstützen. Nicht „Sie können es tun“, sondern „es ist einfach, es ist klar, es ist zu erwarten“.

Zum Beispiel in Pattern-Matching, erhalten Sie die Compiler zu sagen, ob es ein unvollständiges Spiel oder wenn ein anderes Spiel wird nie getroffen werden. Dies ist weniger nützlich mit offenem Ende Typ, aber wenn eine diskriminierte Vereinigung oder Tupeln passend, es ist sehr geschicktes. In F #, erwarten Sie Menschen Muster Spiel, und es macht sofort Sinn.

Das „Problem“ ist, dass, wenn Sie einige funktionale Konzepte beginnen, ist es natürlich auch weiterhin zu wollen. Allerdings wird Tupel, Funktionen, partielle Methode Anwendung und Striegeln, Pattern-Matching, verschachtelte Funktionen, Generika, Monade Unterstützung, etc. in C # nutzen sehr hässlich, sehr schnell. Es macht Spaß, und einig sehr klug Leute haben einige sehr coole Dinge in C # gemacht, aber eigentlich mit es fühlt sich schwer an.

Was ich am Ende mit oft (über-Projekten) in C #:

  • Sequenzfunktionen, über Erweiterungsmethoden für IEnumerable. Dinge wie FürJeden oder Verfahren ( „Übernehmen“ - auf einer Sequenz Elemente eine Aktion tun, wie es aufgezählt wird) passen, da C # Syntax es gut unterstützt
  • .
  • Abstracting gemeinsame Erklärung Muster. Komplizierte try / catch / finally Blöcke oder andere beteiligte (oft stark Generika) Codeblöcke. Erweitern von LINQ-to-SQL paßt auch hier in.
  • Tupeln zu einem gewissen Grad.

** Aber beachten Sie: Der Mangel an automatischer Generalisierung und Typinferenz wirklich die Verwendung auch diese Funktionen behindern. **

Nach diesen Worten zusammen, als jemand anders erwähnt, auf einem kleinen Team, für einen bestimmten Zweck, ja, vielleicht können sie helfen, wenn Sie mit C # stecken. Aber in meiner Erfahrung, sie fühlten sich in der Regel wie mehr Ärger als sie wert waren -. YMMV

Einige andere Verbindungen:

Die wohl der Grund, dass C # es nicht einfach macht auf Typen zu wechseln, weil es in erster Linie eine objektorientierte Sprache ist, und der ‚richtige‘ Weg, dies in Bezug auf objektorientiert zu tun wäre, eine GetRentPrice Methode definieren auf Fahrzeug und es in abgeleiteten Klassen überschrieben werden.

Das heißt, ich habe ein wenig Zeit damit verbracht, das Spiel mit Multi-Paradigma und funktionalen Sprachen wie F # und Haskell, die diese Art von Fähigkeit haben, und ich habe in einer Reihe von Orten kommen, wo es nützlich wäre, vor ( zB wenn Sie schreiben, nicht die Typen, die Sie auf wechseln müssen, so dass Sie nicht eine virtuelle Methode auf sie) implementieren können, und es ist etwas, das ich in die Sprache begrüßen würde zusammen mit diskriminierte Gewerkschaften.

[Edit: Entfernte Teil über die Leistung als Marc angegeben es kurzgeschlossen werden könnten]

Ein weiteres mögliches Problem ist ein Usability ein - aus dem letzten Aufruf ist klar, was passiert, wenn das Spiel keine Bedingungen nicht erfüllt, geschieht, aber was ist das Verhalten, wenn sie paßt zwei oder mehr Bedingungen? Sollte es eine Ausnahme auslösen? Sollte es das erste oder das letzte Spiel zurückkehren?

Ein Weg, den ich zu verwenden neige dazu, diese Art von Problem zu lösen, ist ein Wörterbuch Feld mit dem Typ als Schlüssel und das Lambda als Wert zu verwenden, das ist ziemlich kurz und bündig ist mit Objektinitialisierer Syntax zu konstruieren; aber diese Konten nur für die konkrete Art und erlaubt keine zusätzlichen Prädikate so nicht für komplexere Fälle geeignet sein können. [Randnotiz - wenn Sie den Ausgang der C # Compiler betrachten es wandelt häufig Aussagen Wörterbuch-basierten Schalter Sprungtabellen, so scheint es nicht ein guter Grund zu sein, es nicht auf Typen unterstützen könnte Schalt]

Ich glaube nicht, diese Art von Bibliotheken (die wie Spracherweiterungen handeln) wahrscheinlich eine breite Akzeptanz zu gewinnen, aber sie sind Spaß zu spielen, und kann für kleine Teams wirklich nützlich sein, in bestimmten Bereichen arbeiten, in denen dies nützlich . Zum Beispiel, wenn Sie Tonnen von Geschäftsregeln / Logik "schreiben, die willkürliche Art Tests wie diese und so weiter tut, kann ich sehen, wie es praktisch wäre.

Ich habe keine Ahnung, ob dies überhaupt wahrscheinlich ist eine C # Sprache-Funktion sein (erscheint zweifelhaft, aber wer in die Zukunft sehen kann?).

Als Referenz die entsprechende F # ist etwa:

let getRentPrice (v : Vehicle) = 
    match v with
    | :? Motorcycle as bike -> 100 + bike.Cylinders * 10
    | :? Bicycle -> 30
    | :? Car as car when car.EngineType = Diesel -> 220 + car.Doors * 20
    | :? Car as car when car.EngineType = Gasoline -> 200 + car.Doors * 20
    | _ -> failwith "blah"

vorausgesetzt, Sie eine Klassenhierarchie entlang der Linien von

definiert würden
type Vehicle() = class end

type Motorcycle(cyl : int) = 
    inherit Vehicle()
    member this.Cylinders = cyl

type Bicycle() = inherit Vehicle()

type EngineType = Diesel | Gasoline

type Car(engType : EngineType, doors : int) = 
    inherit Vehicle()
    member this.EngineType = engType
    member this.Doors = doors

Um Ihre Frage zu beantworten, ja, ich denke, Pattern-Matching-syntaktische Konstrukte nützlich sind. Ich für meinen Teil möchte für sie syntaktische Unterstützung in C # sehen.

Hier ist meine Implementierung einer Klasse, die (fast) die gleiche Syntax wie Sie beschreiben,

bietet
public class PatternMatcher<Output>
{
    List<Tuple<Predicate<Object>, Func<Object, Output>>> cases = new List<Tuple<Predicate<object>,Func<object,Output>>>();

    public PatternMatcher() { }        

    public PatternMatcher<Output> Case(Predicate<Object> condition, Func<Object, Output> function)
    {
        cases.Add(new Tuple<Predicate<Object>, Func<Object, Output>>(condition, function));
        return this;
    }

    public PatternMatcher<Output> Case<T>(Predicate<T> condition, Func<T, Output> function)
    {
        return Case(
            o => o is T && condition((T)o), 
            o => function((T)o));
    }

    public PatternMatcher<Output> Case<T>(Func<T, Output> function)
    {
        return Case(
            o => o is T, 
            o => function((T)o));
    }

    public PatternMatcher<Output> Case<T>(Predicate<T> condition, Output o)
    {
        return Case(condition, x => o);
    }

    public PatternMatcher<Output> Case<T>(Output o)
    {
        return Case<T>(x => o);
    }

    public PatternMatcher<Output> Default(Func<Object, Output> function)
    {
        return Case(o => true, function);
    }

    public PatternMatcher<Output> Default(Output o)
    {
        return Default(x => o);
    }

    public Output Match(Object o)
    {
        foreach (var tuple in cases)
            if (tuple.Item1(o))
                return tuple.Item2(o);
        throw new Exception("Failed to match");
    }
}

Hier finden Sie einige Testcode:

    public enum EngineType
    {
        Diesel,
        Gasoline
    }

    public class Bicycle
    {
        public int Cylinders;
    }

    public class Car
    {
        public EngineType EngineType;
        public int Doors;
    }

    public class MotorCycle
    {
        public int Cylinders;
    }

    public void Run()
    {
        var getRentPrice = new PatternMatcher<int>()
            .Case<MotorCycle>(bike => 100 + bike.Cylinders * 10) 
            .Case<Bicycle>(30) 
            .Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20)
            .Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20)
            .Default(0);

        var vehicles = new object[] {
            new Car { EngineType = EngineType.Diesel, Doors = 2 },
            new Car { EngineType = EngineType.Diesel, Doors = 4 },
            new Car { EngineType = EngineType.Gasoline, Doors = 3 },
            new Car { EngineType = EngineType.Gasoline, Doors = 5 },
            new Bicycle(),
            new MotorCycle { Cylinders = 2 },
            new MotorCycle { Cylinders = 3 },
        };

        foreach (var v in vehicles)
        {
            Console.WriteLine("Vehicle of type {0} costs {1} to rent", v.GetType(), getRentPrice.Match(v));
        }
    }

IMHO der OO Weg, um solche Dinge zu tun, ist das Besuchermuster. Ihr Besucher Mitglied Methoden wirken einfach als Fall-Konstrukte und lassen Sie die Sprache selbst die entsprechende Dispatch behandeln, ohne auf Typen „Peek“.

Es ist zwar nicht sehr ‚C-sharpey‘ ist auf Typen zu wechseln, weiß ich, dass Konstrukt würde im allgemeinen Gebrauch ziemlich hilfreich sein - ich zumindest ein persönliches Projekt, das es (obwohl seine managable ATM) verwenden könnte. Gibt es viel von einem Kompilierung Performance-Problem, mit dem Ausdrucksbaum Umschreiben?

Ich denke, das ist wirklich interessant aussieht (+1), aber eine Sache von vorsichtig zu sein: der C # -Compiler zu optimieren Switch-Anweisungen ziemlich gut. Nicht nur für Kurzschlüsse - man bekommt ganz andere IL je nachdem, wie viele Fälle haben Sie und so weiter

.

Ihr konkretes Beispiel tut etwas tun würde ich sehr nützlich -. Gibt es keine Syntax äquivalent von Typ Fall, wie (zum Beispiel) typeof(Motorcycle) keine Konstante ist

Dies wird es noch interessanter in dynamischer Anwendung -. Ihre hier Logik leicht datengesteuerte sein könnte, was ‚regel Motor‘ -Stil Ausführung

können Sie erreichen, was Sie sind, nachdem sie von einer Bibliothek Ich schrieb, genannt OneOf

Der große Vorteil gegenüber switch (und if und exceptions as control flow) ist, dass es Compiler-sicher ist - es gibt keine Standard-Handler oder fallen durch

   OneOf<Motorcycle, Bicycle, Car> vehicle = ... //assign from one of those types
   var getRentPrice = vehicle
        .Match(
            bike => 100 + bike.Cylinders * 10, // "bike" here is typed as Motorcycle
            bike => 30, // returns a constant
            car => car.EngineType.Match(
                diesel => 220 + car.Doors * 20
                petrol => 200 + car.Doors * 20
            )
        );

Es ist auf Nuget und Ziele net451 und netstandard1.6

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top