web-dev-qa-db-fra.com

Expression lambda dynamique avec paramètre dynamique

Étant donné cette classe

public class Foo
{
    public string Name { get; set; }
}

Cette méthode (dans une autre classe) ...

private Func<Foo, string> Compile(string body)
{
    ParameterExpression prm = Expression.Parameter(typeof(Foo), "foo");
    LambdaExpression exp = DynamicExpressionParser.ParseLambda(new[] { prm }, typeof(string), body);
    return (Func<Foo, string>)exp.Compile();
}

Prendra le côté droit d'une expression lambda et me rendra un délégué. Donc si on l’appelle comme ça:

Foo f = new Foo { Name = "Hamilton Academicals" };
//foo => foo.Name.Substring(0,3)
Func<Foo, string> fn = Compile("foo.Name.Substring(0,3)");
string sub = fn(f);

Alors sous aura la valeur "Ham".

Tout va bien, cependant, je voudrais créer DynamicObject de la sous-classe Foo (pour pouvoir implémenter TryGetMember afin de déterminer de manière dynamique les valeurs de propriété), je souhaite donc prendre l'expression et obtenir l'équivalent de ceci

Func<dynamic, dynamic> fn = foo => foo.Name.Substring(0,3);

J'ai essayé Expression.Dynamic en utilisant une CallSiteBinder personnalisée, mais cela échoue avec Aucune propriété ou champ Bar existant dans le type Object (lorsque j'essaie d'accéder à foo.Bar de manière dynamique). Je suppose que cela est dû au fait que l'appel pour obtenir foo.Bar doit être envoyé de manière dynamique (à l'aide de Expression.Dynamic), mais cela ne fonctionnera pas pour moi car l'objectif principal est qu'un utilisateur puisse entrer une expression simple et la faire exécuter. C'est possible?

14
Ron Idaho

Ça marche, si ça peut t'aider:

Compilateur :

using System;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;


namespace ConsoleApp4
{
    public class Compiler
    {
        public static Func<CustomDynamic, TResult> Compile<TResult>(string body)
        {
            ParameterExpression prm = Expression.Parameter(typeof(CustomDynamic), typeof(CustomDynamic).Name);
            LambdaExpression exp = DynamicExpressionParser.ParseLambda(new[] { prm }, typeof(TResult), body);
            return (Func<CustomDynamic, TResult>)exp.Compile();
        }
    }
}

Objet dynamique :

using System.Collections.Generic;
using System.Dynamic;

namespace ConsoleApp4
{
    public class CustomDynamic
    {
        ExpandoObject _values;
        public CustomDynamic()
        {
            _values = new ExpandoObject();
        }

        public void AddProperty(string propertyName, object propertyValue)
        {
            var expandoDict = _values as IDictionary<string, object>;
            if (expandoDict.ContainsKey(propertyName))
                expandoDict[propertyName] = propertyValue;
            else
                expandoDict.Add(propertyName, propertyValue);
        }

        public string GetString(string propertyName)
        {
            var expandoDict = _values as IDictionary<string, object>;
            if (expandoDict.ContainsKey(propertyName))
                return (string)expandoDict[propertyName];
            else
                throw new KeyNotFoundException($"dynamic object did not contain property {propertyName}");
        }
        public int GetInt(string propertyName)
        {
            var expandoDict = _values as IDictionary<string, object>;
            if (expandoDict.ContainsKey(propertyName))
                return (int)expandoDict[propertyName];
            else
                throw new KeyNotFoundException($"dynamic object did not contain property {propertyName}");
        }
    }
}

Cas d'utilisation:

using System;

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            CustomDynamic f = new CustomDynamic();
            f.AddProperty("Name", "Hamiltonian Physics");

            Func<CustomDynamic, string> fn = Compiler.Compile<string>("CustomDynamic.GetString(\"Name\").SubString(0, 3)");

            string sub = fn(f);

            Console.WriteLine(sub);
            Console.ReadLine();
        }
    }
}

Il utilise simplement la classe ExpandoObject. Il est regrettable que vous ayez besoin de passer par son API pour créer des objets, tels que "AddProperty" pour chaque propriété, mais heyho.

Le DynamicExpressionParser.ParseLambda est également un problème pour les méthodes génériques. J'ai donc dû créer des accesseurs spécifiques à un type, ce qui/ n'est pas le meilleur, mais cela fonctionne.

1
Dan Rayson