0

I am trying to combine expr1 and expr2 to produce an Expression<Func<float>>:

var expr1 = (Expression<Func<ColorComponent>>)(() => _modelRgb.R);
var expr2 = (Expression<Func<ColorComponent, float>>)(s => s.Value);
var expr3 = Expression.Lambda(expr1, expr2.Parameters);

While the call to expr3 does work, its .Body property cannot be casted as MemberExpression.

Here are the debug strings of a manually crafted expression and expr3, obviously they're different:

"() =>  (ColorPicker.ColorPickerWindow2)._modelRgb.R.Value"
"s => () =>  (ColorPicker.ColorPickerWindow2)._modelRgb.R"

The question is:

What is the correct way to make expr3 a MemberExpression instead of a LambdaExpression ?

What I'm trying to achieve:

I'd like to pass expressions like () => _modelRgb.R that points to a ColorComponent to a method, and in this method I'd like to build numerous expression to some of its members.

aybe
  • 15,516
  • 9
  • 57
  • 105

1 Answers1

2

What you're fundamentally trying to do here is to compose two expressions. Here is a solution showing how to do that, although it requires a bit of adapdation in order to have the first expression have no parameters, rather than one parameter.

The adapted Compose method would look like this:

public static Expression<Func<TResult>> Compose<TSource, TResult>(
    this Expression<Func<TSource>> first,
    Expression<Func<TSource, TResult>> second)
{
    return Expression.Lambda<Func<TResult>>(
        second.Body.Replace(second.Parameters[0], first.Body));
}

This would use the same Replace method as the linked question, without needing any adaptation:

public class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }

    public override Expression Visit(Expression ex)
    {
        if (ex == from) return to;
        else return base.Visit(ex);
    }
}
public static Expression Replace(this Expression ex,
    Expression from,
    Expression to)
{
    return new ReplaceVisitor(from, to).Visit(ex);
}

By generalizing the code using the above methods you ensure that the code will work regardless of the contents of either expression, rather than writing a method that makes assumptions about what can or can't be in either expression, or to handle a bunch of different cases differently.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • 2
    In the specific case of the original poster this looks correct; it would produce `() => _modelRgb.R.Value`. But for more complex cases there are issues; beta reduction substitution like this is only valid in languages without side effects. For example, if you wanted to combine `()=>M()` and `s=>N(s, s)` then your straightforward beta reduction to `()=>N(M(),M())` has different semantics than the more complex `()=>(s=>N(s,s))((()=>M())())`. The former calls `M()` twice; the latter only calls it once; this matters if `M()` is not idempotent. – Eric Lippert Jan 16 '18 at 19:51
  • @EricLippert True, although it's *very* rare to see expressions used in situations where that would be relevant (as the primary reason to have an expression is because there is code inspecting the expression, rather than simply compiling it and running it as C# code; if they just want to compile and run the code, they can accurately compose the operations as compiled expressions trivially, by just compiling the expressions *before* composing them). – Servy Jan 16 '18 at 20:08
  • Likewise, pretty much all of the code inspecting these expressions (most notably query providers trying to translate the code to SQL) wouldn't be able to understand any form of translation that didn't have the problems you've mentioned (i.e. by storing the result in a variable and then using that), so there isn't really anything you can *do* about that. But it's entirely correct that there are uncommon situations where it's important to understand that that's what's going on. – Servy Jan 16 '18 at 20:08
  • Thanks for all these explanations, I really would like to understand expressions better. The thing is, there's little to no examples in the MSDN documentation, which makes it a very difficult to approach subject. – aybe Jan 16 '18 at 20:10
  • 1
    @Aybe: My apologies. I was an advocate for a proper specification and more MSDN documentation when the feature was being designed, implemented and tested, but sadly I did not get my way. There have been improvements in the subsequent decade, but it still could be a lot better. – Eric Lippert Jan 16 '18 at 20:26
  • It'll surface up up over time, eventually :) – aybe Jan 16 '18 at 23:03