2009-04-03 5 views
12

J'ai une méthode qui modifie un objet « compte » sur la base du délégué de l'action passée en elle:Existe-t-il un moyen facile d'analyser une chaîne (expression lambda) dans un délégué d'action?

public static void AlterAccount(string AccountID, Action<Account> AccountAction) { 
    Account someAccount = accountRepository.GetAccount(AccountID); 
    AccountAction.Invoke(someAccount); 
    someAccount.Save(); 
} 

Cela fonctionne comme prévu ...

AlterAccount("Account1234", a => a.Enabled = false); 

... mais maintenant ce que je « aimerais essayer de faire est d'avoir une méthode comme ceci:

public static void AlterAccount(string AccountID, string AccountActionText) { 
    Account someAccount = accountRepository.GetAccount(AccountID); 
    Action<Account> AccountAction = MagicLibrary.ConvertMagically<Action<Account>>(AccountActionText); 
    AccountAction.Invoke(someAccount); 
    someAccount.Save(); 
} 

Il peut alors être utilisé comme:

AlterAccount("Account1234", "a => a.Enabled = false"); 

pour désactiver le compte "Compte1234".

J'ai jeté un oeil à la linq dynamic query library, qui semble faire plus ou moins ce que je veux, mais pour les délégués de type Func, et ma connaissance des arbres d'expression, etc n'est pas assez bon pour savoir comment réaliser quoi Je veux.

Existe-t-il un moyen facile de faire ce que je veux, ou ai-je besoin d'apprendre des expressions correctement et écrire une charge de code?

(La raison pour laquelle je veux faire est de permettre un moyen facile d'objets de compte de mise à jour en vrac à partir d'un script Powershell où l'utilisateur peut spécifier une expression lambda pour effectuer les modifications.)

+0

la dure serait d'utiliser System.Reflection.Emit.DynamicMethod et générer du code IL. Cependant, dans ce cas, il peut être plus facile d'accepter une syntaxe qui spécifie un nom de propriété et une valeur, et d'avoir un setter de propriétés de base de réflexion. – eulerfx

+0

Oui, j'ai pensé à cela, mais je pense que cela exclurait des choses potentiellement puissantes, comme être capable d'appeler AlterAccount ("Account1234", "a => a.Enabled =! A.Enabled"); (Je ne sais pas pourquoi vous voudriez faire cela spécifiquement, mais il y a d'autres exemples plus raisonnables) – Whisk

+2

J'ai eu un bon rire en lisant ceci: 'MagicLibrary.ConvertMagically' –

Répondre

1

Il n'y a pas de façon générale pour analyser une chaîne dans une expression lambda sans compilation complète, car les expressions lambda peuvent référencer des éléments définis en dehors de l'expression lambda. Je ne connais aucune bibliothèque qui gère le cas spécifique que vous voulez. Il y a une longue discussion sur un thread sur un groupe de discussion C#.

La méthode la plus simple pour obtenir ce que vous voulez est de compiler une méthode lors de l'exécution. Vous pouvez écrire une fonction qui accepte la chaîne "a.Enabled = true; return a;" et colle cela au milieu d'une fonction qui prend un compte en paramètre. Je voudrais utiliser ce library comme point de départ, mais vous pouvez également utiliser la fonction mentionnée sur another thread.

+0

Merci - D'un essai rapide qui ne semble pas fonctionner lors de l'évaluation une chaîne comme "a => a.Enabled = true" à un délégué d'action - est-ce que je fais quelque chose de mal ou cela ne fonctionnera-t-il pas car ce n'est pas une déclaration C# complète en elle-même? J'ai mis à jour la question pour clarifier. – Whisk

+0

rossfabricant; la bibliothèque linq dynamique qu'il mentionne fait exactement cela. Bien sûr, vous devez fournir des informations de frappe lorsque vous allez compiler, mais cela compile bien. – MichaelGG

+0

Il peut construire des requêtes mais je n'ai pas vu qu'il pourrait construire quelque chose qui modifie l'état. – RossFabricant

3

La bibliothèque Dynamic LINQ est un excellent choix, car elle génère des expressions que vous pouvez compiler pour coder de manière légère.

L'exemple que vous avez fourni produit en réalité un booléen - vous devriez donc pouvoir demander un Func et il pourrait le trier. Editer: Ceci est bien sûr faux, car les expressions n'ont pas d'affectation du tout. Donc, un autre moyen est de prendre deux lambdas. L'un pour trouver la propriété que vous voulez, un pour fournir une valeur:

(a => a.AccountId), (a => true)

Ensuite, utilisez la réflexion pour définir la propriété référencée dans la première lambda avec le résultat du second. Hackish, mais c'est quand même probablement léger comparé à l'invocation du compilateur C#. De cette façon, vous n'avez pas à faire beaucoup de codegen vous-même - les expressions que vous obtenez contiendront presque tout ce dont vous avez besoin.

+0

Je pense ... Si je faisais "a => a.Enabled == false" alors cela fonctionnerait, mais je fais "a => a.Enabled = false" - Je veux faire cette action sur le Compte, donc je ne pense pas que cela fonctionnera sans quelques modifications. – Whisk

+0

Bah tu as raison. Cela fonctionne pour funcs: Func exp =() => test = false; mais pas pour les expressions, car ils n'ont aucun moyen de représenter l'affectation. – MichaelGG

+0

Yep - Je pense que je vais peut-être descendre la route d'essayer de modifier les choses Dynamic LINQ pour travailler avec l'action ainsi que Func ... – Whisk

0

C'est facile:

  • Utilisez CodeDom pour générer le module contenant la « classe environnante », vous allez utiliser pour construire l'expression; cette classe doit implémenter l'interface connue de votre application.
  • Utilisez CodeSnippedExpression pour injecter l'expression dans son membre.
  • Utilisez le type Activator pour créer l'instance de cette classe en cours d'exécution.

Fondamentalement, vous devez construire la classe suivante avec CodeDom:

using System; 
using MyNamespace1; 
using ... 
using MyNamespace[N]; 

namespace MyNamespace.GeneratedTypes 
{ 
    public class ExpressionContainer[M] : IHasAccountAction 
    { 
    public Action<Account> AccountAction { 
     get { 
     return [CodeSnippedExpression must be used here]; 
     } 
    } 
    } 
} 

En supposant que IHasAccountAction est:

public IHasAccountAction { 
    public Action<Account> AccountAction { get; } 
} 

Si cela est fait, vous pouvez obtenir l'expression compilée à partir de la chaîne avec facilité. Si vous avez besoin de sa représentation en arbre d'expression, utilisez Expression<Action<Account>> au lieu de Action<Account> dans le type généré.

+0

(a répondu à votre commentaire sur les opérateurs génériques, btw) –

3

Vous pouvez essayer ceci: Dynamic Lambda Expressions Using An Isolated AppDomain

Il compile une expression lambda en utilisant le compilateur CodeDOM. Afin de disposer de l'ensemble en mémoire qui est créé, le compilateur fonctionne sur un AppDomain isolé. Pour le passage de l'expression à travers la frontière de domaine, il doit être sérialisé. Hélas, Expression<> n'est pas Serializable. Donc, un truc doit être utilisé. Tous les détails sont expliqués dans le message.

Je suis l'auteur de ce composant, soit dit en passant. J'aimerais beaucoup entendre vos commentaires à ce sujet.

Questions connexes