J'ai regardé F # récemment, et bien que je ne sois pas susceptible de sauter la clôture de si tôt, cela met définitivement en évidence certains domaines où C # (ou le support de la bibliothèque) pourrait rendre la vie plus facile.
En particulier, je pense à la capacité de correspondance de modèle de F #, qui permet une syntaxe très riche - beaucoup plus expressive que le commutateur actuel / les équivalents C # conditionnels. Je n'essaierai pas de donner un exemple direct (mon F # n'est pas à la hauteur), mais en bref cela permet:
- correspondance par type (avec vérification de la couverture complète pour les unions discriminées) [notez que cela déduit également le type de la variable liée, donnant l'accès aux membres, etc.]
- correspondance par prédicat
- combinaisons de ce qui précède (et peut-être d'autres scénarios dont je ne suis pas au courant)
Alors qu'il serait bien que C # emprunte éventuellement [ahem] une partie de cette richesse, dans l'intervalle, j'ai examiné ce qui peut être fait au moment de l'exécution - par exemple, il est assez facile d'assembler certains objets pour permettre:
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
où getRentPrice est un Func <Vehicle, int>.
[note - peut-être que Switch / Case est ici les mauvais termes ... mais cela montre l'idée]
Pour moi, c'est beaucoup plus clair que l'équivalent en utilisant if / else répété, ou un conditionnel ternaire composite (qui devient très compliqué pour les expressions non triviales - des crochets à gogo). Cela évite également beaucoup de cast, et permet une simple extension (soit directement, soit via des méthodes d'extension) à des correspondances plus spécifiques, par exemple une correspondance InRange (...) comparable au VB Select ... Case "x To y "utilisation.
J'essaie juste de déterminer si les gens pensent qu'il y a beaucoup d'avantages à des constructions comme celles-ci (en l'absence de support linguistique)?
Notez en outre que j'ai joué avec 3 variantes de ce qui précède:
- une version Func <TSource, TValue> pour l'évaluation - comparable aux instructions conditionnelles ternaires composites
- une version Action <TSource> - comparable à if / else if / else if / else if / else
- une version Expression <Func <TSource, TValue >> - comme la première, mais utilisable par des fournisseurs LINQ arbitraires
De plus, l'utilisation de la version basée sur les expressions permet la réécriture de l'arborescence des expressions, en incorporant essentiellement toutes les branches dans une seule expression conditionnelle composite, plutôt que d'utiliser des appels répétés. Je n'ai pas vérifié récemment, mais dans certaines premières versions d'Entity Framework, je semble me souvenir que cela était nécessaire, car il n'aimait pas beaucoup InvocationExpression. Il permet également une utilisation plus efficace avec LINQ-to-Objects, car il évite les invocations répétées de délégués - les tests montrent une correspondance comme celle ci-dessus (en utilisant le formulaire Expression) fonctionnant à la même vitesse [légèrement plus rapide, en fait] par rapport à l'équivalent C # instruction conditionnelle composite. Par souci d'exhaustivité, la version basée sur Func <...> a pris 4 fois plus de temps que l'instruction conditionnelle C #, mais elle est toujours très rapide et ne sera probablement pas un goulot d'étranglement majeur dans la plupart des cas d'utilisation.
J'apprécie toute réflexion / entrée / critique / etc sur ce qui précède (ou sur les possibilités d'un support plus riche du langage C # ... ici en espérant ;-p).
la source
switch-case
déclaration. Ne vous méprenez pas, je pense qu'il a sa place et je chercherai probablement un moyen de le mettre en œuvre.Réponses:
Je sais que c'est un vieux sujet, mais en c # 7, vous pouvez faire:
la source
Après avoir essayé de faire de telles choses "fonctionnelles" en C # (et même essayé un livre dessus), je suis arrivé à la conclusion que non, à quelques exceptions près, de telles choses n'aident pas trop.
La raison principale est que des langages tels que F # tirent beaucoup de leur puissance de la prise en charge réelle de ces fonctionnalités. Pas "tu peux le faire", mais "c'est simple, c'est clair, c'est attendu".
Par exemple, dans la correspondance de modèles, vous obtenez le compilateur vous indiquant s'il y a une correspondance incomplète ou lorsqu'une autre correspondance ne sera jamais atteinte. C'est moins utile avec les types ouverts, mais lors de la mise en correspondance d'une union discriminée ou de tuples, c'est très pratique. En F #, vous vous attendez à ce que les gens correspondent aux modèles, et cela a instantanément du sens.
Le «problème» est qu'une fois que vous commencez à utiliser certains concepts fonctionnels, il est naturel de vouloir continuer. Cependant, tirer parti des tuples, des fonctions, de l'application de méthode partielle et du currying, de la correspondance de modèles, des fonctions imbriquées, des génériques, du support des monades, etc. en C # devient très laid, très rapidement. C'est amusant, et certaines personnes très intelligentes ont fait des choses très cool en C #, mais en fait, l' utiliser semble lourd.
Ce que j'ai fini par utiliser souvent (entre projets) en C #:
** Mais notez: le manque de généralisation automatique et d'inférence de type entrave vraiment l'utilisation même de ces fonctionnalités. **
Tout cela dit, comme quelqu'un l'a mentionné, dans une petite équipe, dans un but précis, oui, peut-être qu'ils peuvent vous aider si vous êtes coincé avec C #. Mais d'après mon expérience, ils se sentaient généralement plus compliqués qu'ils n'en valaient la peine - YMMV.
Quelques autres liens:
la source
On peut soutenir que la raison pour laquelle C # ne facilite pas l'activation du type est qu'il s'agit principalement d'un langage orienté objet, et la manière `` correcte '' de le faire en termes orientés objet serait de définir une méthode GetRentPrice sur Vehicle et le remplacer dans les classes dérivées.
Cela dit, j'ai passé un peu de temps à jouer avec des langages multi-paradigmes et fonctionnels comme F # et Haskell qui ont ce type de capacité, et je suis tombé sur un certain nombre d'endroits où cela serait utile auparavant (par exemple, lorsque vous n'écrivez pas les types que vous devez activer afin que vous ne puissiez pas implémenter une méthode virtuelle sur eux) et c'est quelque chose que je serais heureux de retrouver dans la langue avec les syndicats discriminés.
[Edit: Suppression de la partie sur les performances car Marc a indiqué qu'elle pourrait être court-circuitée]
Un autre problème potentiel est celui de la convivialité - il est clair à partir de l'appel final que se passe-t-il si la correspondance ne remplit aucune condition, mais quel est le comportement si elle correspond à deux conditions ou plus? Doit-il lever une exception? Doit-il renvoyer le premier ou le dernier match?
Une façon que j'ai tendance à utiliser pour résoudre ce genre de problème est d'utiliser un champ de dictionnaire avec le type comme clé et le lambda comme valeur, ce qui est assez laconique à construire en utilisant la syntaxe d'initialisation d'objet; cependant, cela ne tient compte que du type concret et n'autorise pas de prédicats supplémentaires et peut donc ne pas convenir à des cas plus complexes. [Note latérale - si vous regardez la sortie du compilateur C #, il convertit fréquemment les instructions switch en tables de sauts basées sur le dictionnaire, il ne semble donc pas y avoir de bonne raison pour laquelle il ne pouvait pas prendre en charge le changement de types]
la source
Je ne pense pas que ces types de bibliothèques (qui agissent comme des extensions de langage) sont susceptibles d'être largement acceptées, mais elles sont amusantes à jouer et peuvent être vraiment utiles pour les petites équipes travaillant dans des domaines spécifiques où cela est utile. Par exemple, si vous écrivez des tonnes de «règles / logiques métier» qui effectuent des tests de type arbitraires comme celui-ci et ainsi de suite, je peux voir à quel point ce serait pratique.
Je ne sais pas si cela est susceptible d'être une fonctionnalité du langage C # (cela semble douteux, mais qui peut voir l'avenir?).
Pour référence, le F # correspondant est approximativement:
en supposant que vous ayez défini une hiérarchie de classes sur le modèle de
la source
Pour répondre à votre question, oui, je pense que les constructions syntaxiques de correspondance de motifs sont utiles. Pour ma part, j'aimerais voir le support syntaxique en C # pour cela.
Voici mon implémentation d'une classe qui fournit (presque) la même syntaxe que celle que vous décrivez
Voici un code de test:
la source
Correspondance de modèle (comme décrit ici ), son but est de déconstruire les valeurs en fonction de leur spécification de type. Cependant, le concept d'une classe (ou d'un type) en C # ne vous convient pas.
Il n'y a pas de problème avec la conception de langage multi-paradigme, au contraire, c'est très bien d'avoir des lambdas en C #, et Haskell peut faire des choses impératives, par exemple IO. Mais ce n'est pas une solution très élégante, pas à la mode Haskell.
Mais comme les langages de programmation procédurale séquentielle peuvent être compris en termes de calcul lambda, et que C # se trouve bien dans les paramètres d'un langage procédural séquentiel, c'est un bon ajustement. Mais, prendre quelque chose du pur contexte fonctionnel de dire Haskell, puis mettre cette caractéristique dans un langage qui n'est pas pur, eh bien, faire juste cela ne garantira pas un meilleur résultat.
Mon point est le suivant: ce qui fait cocher la correspondance de modèle est lié à la conception du langage et au modèle de données. Cela dit, je ne pense pas que la correspondance de modèles soit une fonctionnalité utile de C # car elle ne résout pas les problèmes typiques de C # et ne rentre pas bien dans le paradigme de programmation impératif.
la source
IMHO la façon OO de faire de telles choses est le modèle de visiteur. Vos méthodes de membre visiteur agissent simplement comme des constructions de cas et vous laissez le langage lui-même gérer la répartition appropriée sans avoir à «jeter un coup d'œil» aux types.
la source
Bien que ce ne soit pas très «C-sharpey» pour activer le type, je sais que cette construction serait très utile en utilisation générale - j'ai au moins un projet personnel qui pourrait l'utiliser (bien que son ATM gérable). Y a-t-il un gros problème de performances de compilation, avec la réécriture de l'arbre d'expression?
la source
Je pense que cela semble vraiment intéressant (+1), mais une chose à faire attention: le compilateur C # est assez bon pour optimiser les instructions de commutation. Pas seulement pour les courts-circuits - vous obtenez une IL complètement différente en fonction du nombre de cas que vous avez et ainsi de suite.
Votre exemple spécifique fait quelque chose que je trouverais très utile - il n'y a pas de syntaxe équivalente à cas par type, car (par exemple)
typeof(Motorcycle)
n'est pas une constante.Cela devient plus intéressant dans les applications dynamiques - votre logique ici pourrait être facilement basée sur les données, donnant une exécution de style «moteur de règles».
la source
Vous pouvez réaliser ce que vous recherchez en utilisant une bibliothèque que j'ai écrite, appelée OneOf
Le principal avantage par rapport à
switch
(etif
etexceptions as control flow
) est qu'il est sûr au moment de la compilation - il n'y a pas de gestionnaire par défaut ou d'interruptionIl est sur Nuget et cible net451 et netstandard1.6
la source