Generating Action with Expressions as strings

c# dynamic expression-trees

Question

I'm trying to figure out how to generate an Action from a collection of strings the represent the actions "statement" lines ...

using System.Linq.Dynamic;

Action<T> BuildAction<T>(T sourceObject, T destinationObject) where T : BaseThing
{
    var source = Expression.Parameter(sourceObject.GetType(), "source");
    var destination = Expression.Parameter(destinationObject.GetType(), "destination");

    var statements = new[] {
        "destination.Foo = source.Foo",
        "destination.X = source.Y"
    };

    var parsedStatements = statements.Select(s => DynamicExpression.Parse(new[] { destination, source }, typeof(void), s);

    return Expression.Lambda<Action<T>>(Expression.Block(parsedStatements));
}

The idea is to end up with something like ...

Action<T> result = (destination, source) => {
     destination.Foo = source.Foo;
     destination.X = source.Y;
};

The other issue i have is that source and destination don't have to be the same type, they only share a base type, so in this example destination may not have a Y property and source may not have an X property (hense the mapping).

An Update

So I have a partial solution, although this is making a ton of assumptions I want to remove and it only maps {destination}.Foo = {source}.Bar type stuff and can't drill any deeper at the moment I figured this might help others to determine where I am going with this and thus help me find a more complete solution ...

So as I explainedi n the comments, this is a small piece of how my workflow engine works, the idea is to execute activities and then as part of the internal engine it generates this Action to copy computed values to the next activity before execution.

I have this struct ...

struct PropertySourceInfo
{
    public Activity Source { get; set; }
    public Activity Destination { get; set; }
    public Link Link { get; set; }
}

Which is returned by "SourceInfoFor(activity, p)" in the code below with the select block being the root cause of my question ...

Action<Activity> BuildAssign(Activity activity, Flow flow)
{
    var type = activity.GetType();
    var destination = Expression.Parameter(typeof(Activity), "activity");

    // build property mappings
    var assigns = type.GetProperties()
        .Where(p => IsPreviousActivityInput(activity, p))
        .Select(p => {
            var info = SourceInfoFor(activity, p, flow);
            if (info != null)
            {
                var i = info.Value;
                var sidx = activity.Previous.IndexOf(sa => sa == i.Source);
                var sType = activity.Previous[sidx].GetType().GetCSharpTypeName();

                // ok my assumption here is that I have something like this ...
                // {destination}.Property = {Source}.Property
                // ... so breaking it up I can then build the Expression needed for each part: 
                var assignParts = i.Link.Expression.Split(' ');

                //TODO: do this more intelligently to handle "sub property value passing"
                var destExpr = Expression.Property(Expression.Convert(destination, type), assignParts[0].Split(".".ToCharArray()).Last());
                var destArray = Expression.Property(destination, type, "Previous");
                var sourceActivity = Expression.ArrayAccess(destArray, Expression.Constant(sidx));
                var sourceExpr = Expression.Property(Expression.Convert(sourceActivity, activity.Previous[sidx].GetType()), assignParts[2].Split(".".ToCharArray()).Last());

                var result = Expression.Assign(destExpr, sourceExpr);
                return result;
            }
            else
                return null;
        })
        .Where(i => i != null)
        .ToArray();

    // the complete block as a single "Action<TActivity>" that can be called
    if (assigns.Any())
    {
        var result = Expression.Lambda<Action<Activity>>(Expression.Block(assigns), destination);
        log.Debug(result.ToString());
        return result.Compile();
    }
    else
        return null;
}

Please note

For the form factor that stack requires of us when posing a question I felt that posing my complete problem domain was too bigger problem so whilst this question may be solvable in other ways on this occasion I need it solved this way for external to the question reasons.

I also like and want a deeper understanding of expression trees!

1
0
8/9/2018 1:21:53 PM

Accepted Answer

So it turns out that the answer to this was not as simple as I had hoped. In short ... I need to write an expression parser.

For the simplest case (the one posited in the question), I can use the code in my partial solution, but for the full solution i'm going to have to build an expression parser that can handle much more complexity put in to the strings.

In my case, using dictionaries or similar approaches only solves a piece of the underlying issue, and I can't use reflection because my situation warrants "reuse of the compiled action at scale" (which i had lightly touched upon in the question).

I could refer to a list of questions with answers that solve various pieces of this problem but i managed to find a more "complete" starting point for what I was trying to achieve in other places ...

https://archive.codeplex.com/?p=simproexpr

... this example can do a bit more than just parse expressions, it's capable of parsing expression blocks too.

Using that / something like that I'm going to build something along those lines to solve my proble, i hope this helps others.

1
8/16/2018 9:44:54 AM


Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow