Building expression tree throws NotSupportedException "TryExpression is not supported as an argument to method.."

StackOverflow https://stackoverflow.com/questions/19625232

  •  01-07-2022
  •  | 
  •  

سؤال

I'm trying to build an expression tree but it throws NotSupportedException when compiled:

TryExpression is not supported as an argument to method 'Boolean TryGetMemberValue(System.Object, System.String, System.Object ByRef)' because it has an argument with by-ref type. Construct the tree so the TryExpression is not nested inside of this expression.

I sort of understand what it says but I can't figure out what I need to change... here's the code:

static void Main()
{
    //A.Child.Name
    var child = CreateExpression("Child", proxyParameter);
    var name = CreateExpression("Name", child);

    var expr = Expression.Lambda<Func<Proxy, object>>(name, proxyParameter);

    var func = expr.Compile();
}

class A
{
    public A Child { get; set; }
    public string Name { get; set; }
}

abstract class Proxy
{
    public abstract bool TryGetMemberValue(object parent, string name, out object result);
}

static ParameterExpression proxyParameter = Expression.Parameter(typeof(Proxy), "proxy");

static Expression CreateExpression(string propertyName, Expression parent)
{
    var tryGetMemberMethod = typeof(Proxy).GetMethod("TryGetMemberValue");

    var result = Expression.Variable(typeof(object), "out result");

    var returnTarget = Expression.Label(typeof(object));

    var tryGetMember =
        Expression.Block(
            new[] { result },
            Expression.IfThenElse(Expression.Equal(Expression.Call(proxyParameter, tryGetMemberMethod, parent, Expression.Constant(propertyName), result), Expression.Constant(true)),
                Expression.Return(returnTarget, result),
                Expression.Throw(Expression.Constant(new MissingMemberException(propertyName)))),
            Expression.Label(returnTarget, Expression.Constant(null)));

    return tryGetMember;
}

Which generates the following expression:

.Lambda #Lambda1<System.Func`2[Program+Proxy,System.Object]>(Program+Proxy $proxy) {
.Block(System.Object $'out result') {
    .If (.Call $proxy.TryGetMemberValue(
        .Block(System.Object $'out result') {
            .If (.Call $proxy.TryGetMemberValue(
                $proxy,
                "Child",
                $'out result') == True) {
                .Return #Label1 { $'out result' }
            } .Else {
                .Throw .Constant<System.MissingMemberException>(System.MissingMemberException: Child)
            };
            .Label
                null
            .LabelTarget #Label1:
        },
        "Name",
        $'out result') == True) {
        .Return #Label2 { $'out result' }
    } .Else {
        .Throw .Constant<System.MissingMemberException>(System.MissingMemberException: Name)
    };
    .Label
        null
    .LabelTarget #Label2:
}

Any ideas?

/* edited */ interestingly enough this works fine:

var tryGetMember =
    Expression.Block(
        new[] { result },
        Expression.Call(proxyParameter, tryGetMemberMethod, parent, Expression.Constant(propertyName), result),
    result);
هل كانت مفيدة؟

المحلول

While the exception message is far from clear, I think you just encountered a limitation of the Expression compiler: you can't have complicated subexpressions under a call expression to a method with ref parameters.

I think what you should do to fix this is basically the same thing you would do in C# code: use a local variable to keep the intermediate result:

var child = Expression.Variable(typeof(object), "child");

var body = Expression.Block(
    new[] { child },
    Expression.Assign(child, CreateExpression("Child", proxyParameter)),
    CreateExpression("Name", child));

var expr = Expression.Lambda<Func<Proxy, object>>(body, proxyParameter);

With this change, the code no longer throws an exception.

Also, you don't need to use Label and Return in your tryGetMember expression. Instead, you could replace IfThenElse with Condition (basically, ternary operator from C#). If you do this, you also need to specify the type of Throw (even though it never actually returns):

var tryGetMember =
    Expression.Block(
        new[] { result },
        Expression.Condition(
            Expression.Equal(
                Expression.Call(
                    proxyParameter, tryGetMemberMethod, parent,
                    Expression.Constant(propertyName), result),
                Expression.Constant(true)),
            result,
            Expression.Throw(
                Expression.Constant(new MissingMemberException(propertyName)),
                typeof(object))));
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top