score:1

Accepted answer

for both of your examples this can actually be done with two expression visitors (code is commented):

static class extensions {
    public static tresult fakeinvoke<tresult>(this delegate instance, params object[] parameters)
    {
        // this is not intended to be called directly
        throw new notimplementedexception();
    }

    public static texpression unwrap<texpression>(this texpression exp) where texpression : expression {
        return (texpression) new fakeinvokevisitor().visit(exp);
    }

    class fakeinvokevisitor : expressionvisitor {
        protected override expression visitmethodcall(methodcallexpression node) {
            // replace fakeinvoke call
            if (node.method.name == "fakeinvoke") {
                // first obtain reference to method being called (so, for c.fakeinvoke(...) that will be "c")
                var func = (delegate)expression.lambda(node.arguments[0]).compile().dynamicinvoke();
                // explore method argument names and types
                var argumentnames = new list<string>();
                var dummyarguments = new list<object>();
                foreach (var arg in func.method.getparameters()) {
                    argumentnames.add(arg.name);
                    // create default value for each argument
                    dummyarguments.add(arg.parametertype.isvaluetype ? activator.createinstance(arg.parametertype) : null);
                }
                // now, invoke function with default arguments to obtain expression (for example, this one () => a*(a + 3)).
                // all arguments will have default value (0 in this case), but they are not literal "0" but a reference to "a" member with value 0
                var exp = (expression) func.dynamicinvoke(dummyarguments.toarray());
                // this is expressions representing what we passed to fakeinvoke (for example expression (x + 3))
                var argumentexpressions = (newarrayexpression)node.arguments[1];
                // now invoke second visitor
                exp = new innerfakeinvokevisitor(argumentexpressions, argumentnames.toarray()).visit(exp);
                return ((lambdaexpression)exp).body;
            }
            return base.visitmethodcall(node);
        }
    }

    class innerfakeinvokevisitor : expressionvisitor {
        private readonly newarrayexpression _args;
        private readonly string[] _argumentnames;
        public innerfakeinvokevisitor(newarrayexpression args, string[] argumentnames) {
            _args =  args;
            _argumentnames = argumentnames;
        }
        protected override expression visitmember(memberexpression node) {
            // if that is a reference to one of our arguments (for example, reference to "a")
            if (_argumentnames.contains(node.member.name)) {
                // find related expression
                var idx = array.indexof(_argumentnames, node.member.name);
                var argument = _args.expressions[idx];
                var unary = argument as unaryexpression;
                // and replace it. so "a" is replaced with expression "x + 3"
                return unary?.operand ?? argument;
            }
            return base.visitmember(node);
        }
    }
}

can be used like this:

func<int, expression<func<int>>> c = (int a) => () => a * (a + 3);
expression<func<int, int>> d = (x) => 2 + c.fakeinvoke<int>(3 + x);
d = d.unwrap(); // this is now "x => (2 + ((3 + x) * ((3 + x) + 3)))"

simple case:

func<int, expression<func<int>>> c = (int a) => () => a + 3;
expression<func<int>> d = () => 2 + c.fakeinvoke<int>(3);
d = d.unwrap(); // this is now "() => 2 + (3 + 3)

with multiple arguments:

func<int, int, expression<func<int>>> c = (int a, int b) => () => a * (a + 3) + b;
expression<func<int, int>> d = (x) => 2 + c.fakeinvoke<int>(3 + x, x + 5);
d = d.unwrap(); // "x => (2 + (((3 + x) * ((3 + x) + 3)) + (x + 5)))"

note that fakeinvoke is not type-safe (you should explicitly set return type and arguments and not checked). but that's just for example, in real use you can create many overloads of fakeinvoke, like this:

public static tresult fakeinvoke<targ, tresult>(this func<targ, expression<func<tresult>>> instance, targ argument) {
        // this is not intended to be called directly
    throw new notimplementedexception();
}

code above should be modified a bit to handle such calls correctly (because arguments are now not in single newarrayexpression), but that's quite easy to do. with such overloads you can just do:

expression<func<int, int>> d = (x) => 2 + c.fakeinvoke(3 + x); // this is type-safe now, you cannot pass non-integer as "3+x", nor you can pass more or less arguments than required.

score:-1

using linqkit, it simple to consume it's expandable query wrapper by calling asexpandable() on the first entity type. this expandable wrapper does the work necessary to compose expressions make them compatible with ef.

a toy example of it's usage is below (person is an ef code first entity) -

var ctx = new test();

expression<func<person, bool>> agefilter = p => p.age < 30;

var filtered = ctx.people.asexpandable()
    .where(p => agefilter.invoke(p) && p.name.startswith("j"));
console.writeline( $"{filtered.count()} people meet the criteria." );

score:0

the case where expressions are returned from lambdas is really hard because those expressions are actually closures that have non-public (system.runtime.compilerservices.closure?) object inside them that contains the values that lambda closes over. all of that makes really hard to accurately replace formal parameters with actual parameters in expression tree.


inspired by evk response i found fairly elegant solution for simpler case:

expression<func<int, int>> c = (int a) => a * (a + 3);
var d = extensions.splice<func<int, int>>((x) => 2 + c.embed(3 + x));

// d is now x => (2 + ((3 + x) * ((3 + x) + 3))) expression

public static class extensions
{
    public static t embed<t>(this expression<func<t>> exp) { throw new exception("should not be executed"); }
    public static t embed<a, t>(this expression<func<a, t>> exp, a a) { throw new exception("should not be executed"); }
    public static t embed<a, b, t>(this expression<func<a, b, t>> exp, a a, b b) { throw new exception("should not be executed"); }
    public static t embed<a, b, c, t>(this expression<func<a, b, c, t>> exp, a a, b b, c c) { throw new exception("should not be executed"); }
    public static t embed<a, b, c, d, t>(this expression<func<a, b, c, d, t>> exp, a a, b b, c c) { throw new exception("should not be executed"); }

    public static expression<t> splice<t>(expression<t> exp)
    {
        return new splicingvisitor().visit(exp) as expression<t>;
    }
    class splicingvisitor : expressionvisitor
    {
        protected override expression visitmethodcall(methodcallexpression node)
        {
            if (node.method.name == "embed")
            {
                var mem = node.arguments[0] as memberexpression;

                var getterlambda = expression.lambda<func<object>>(mem, new parameterexpression[0]);
                var lam = getterlambda.compile().dynamicinvoke() as lambdaexpression;

                var parametermapping = lam.parameters.select((p, index) => new
                {
                    formalparameter = p,
                    actualparameter = node.arguments[index+1]
                }).todictionary(o => o.formalparameter, o => o.actualparameter);

                return new parameterreplacervisitor(parametermapping).visit(lam.body);
            }
            return base.visitmethodcall(node);
        }
    }
    public class parameterreplacervisitor : expressionvisitor
    {
        private dictionary<parameterexpression, expression> parametermapping;
        public parameterreplacervisitor(dictionary<parameterexpression, expression> parametermapping)
        {
            this.parametermapping = parametermapping;
        }

        protected override expression visitparameter(parameterexpression node)
        {
            if(parametermapping.containskey(node))
            {
                return parametermapping[node];
            }
            return base.visitparameter(node);
        }
    }
}

Related Query

More Query from same tag